%---------------------------------------------------------------------------%
% vim: ft=mercury ts=4 sw=4 et
%---------------------------------------------------------------------------%
% Copyright (C) 2000-2012 The University of Melbourne.
% Copyright (C) 2013-2020, 2023-2024 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.
%---------------------------------------------------------------------------%
%
% Output MLDS function declarations and definitions in Java.
%
%---------------------------------------------------------------------------%

:- module ml_backend.mlds_to_java_func.
:- interface.

:- import_module libs.
:- import_module libs.indent.
:- import_module ml_backend.mlds.
:- import_module ml_backend.mlds_to_java_util.
:- import_module ml_backend.mlds_to_target_util.

:- import_module io.
:- import_module list.

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

:- pred output_func_decl_for_java(java_out_info::in, io.text_output_stream::in,
    indent::in, mlds_function_name::in, output_aux::in, mlds_func_params::in,
    io::di, io::uo) is det.

    % This predicate assumes that our caller
    %
    % - has already printed any needed indentation at the start of the
    %   first line, and
    %
    % - it will print the newline after the final parenthesis
    %   that is output by this predicate.
    %
:- pred output_params_for_java(java_out_info::in, io.text_output_stream::in,
    indent::in, list(mlds_argument)::in, io::di, io::uo) is det.

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

:- pred output_function_defn_for_java(java_out_info::in,
    io.text_output_stream::in, indent::in, output_aux::in,
    mlds_function_defn::in, io::di, io::uo) is det.

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

:- implementation.

:- import_module hlds.
:- import_module hlds.hlds_module.
:- import_module ml_backend.mlds_to_java_name.
:- import_module ml_backend.mlds_to_java_stmt.
:- import_module ml_backend.mlds_to_java_type.
:- import_module parse_tree.
:- import_module parse_tree.parse_tree_output.
:- import_module parse_tree.prog_data.

:- import_module bool.
:- import_module maybe.
:- import_module string.
:- import_module uint.

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

output_func_decl_for_java(Info, Stream, Indent, FuncName, OutputAux,
        Signature, !IO) :-
    Signature = mlds_func_params(Parameters, RetTypes),
    ( if
        OutputAux = oa_cname(ClassName, ClassArity),
        FuncName = mlds_function_export("<constructor>")
    then
        ClassNameStr =
            unqual_class_name_to_string_for_java(ClassName, ClassArity),
        io.write_string(Stream, ClassNameStr, !IO)
    else
        output_return_types_for_java(Info, Stream, RetTypes, !IO),
        IndentStr = indent2_string(Indent),
        FuncNameStr = function_name_to_string_for_java(FuncName),
        io.format(Stream, "\n%s%s", [s(IndentStr), s(FuncNameStr)], !IO)
    ),
    output_params_for_java(Info, Stream, Indent, Parameters, !IO).

:- pred output_return_types_for_java(java_out_info::in,
    io.text_output_stream::in, mlds_return_types::in, io::di, io::uo) is det.

output_return_types_for_java(Info, Stream, RetTypes, !IO) :-
    (
        RetTypes = [],
        io.write_string(Stream, "void", !IO)
    ;
        RetTypes = [RetType],
        output_type_for_java(Info, Stream, RetType, !IO)
    ;
        RetTypes = [_, _ | _],
        % For multiple outputs, we return an array of objects.
        io.write_string(Stream, "java.lang.Object []", !IO)
    ).

output_params_for_java(Info, Stream, Indent, Parameters, !IO) :-
    io.write_char(Stream, '(', !IO),
    (
        Parameters = []
    ;
        Parameters = [_ | _],
        io.nl(Stream, !IO),
        Indent1Str = indent2_string(Indent + 1u),
        write_out_list(output_param(Info, Indent1Str), ",\n", Parameters,
            Stream, !IO)
    ),
    io.write_char(Stream, ')', !IO).

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

output_param(Info, IndentStr, Arg, Stream, !IO) :-
    Arg = mlds_argument(VarName, Type, _GCStmt),
    TypeStr = type_to_string_for_java(Info, Type),
    VarNameStr = local_var_name_to_string_for_java(VarName),
    io.format(Stream, "%s%s %s",
        [s(IndentStr), s(TypeStr), s(VarNameStr)], !IO).

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

output_function_defn_for_java(Info, Stream, Indent, OutputAux,
        FunctionDefn, !IO) :-
    % Put a blank line before each function definition.
    io.nl(Stream, !IO),

    FunctionDefn = mlds_function_defn(FuncName, Context, Flags, _Source,
        Params, MaybeBody, _EnvVarNames, _MaybeRequireTailrecInfo),
    (
        MaybeBody = body_external,
        % This is just a function declaration, with no body.
        % Java doesn't support separate declarations and definitions,
        % so just output the declaration as a comment.
        % (Note that the actual definition of an external procedure
        % must be given in `pragma foreign_code' in the same module.)
        %
        % XXX For now, we print only the name of the function.
        % We would like to print the whole declaration in a comment,
        % but that does not work. For some argument types in the function
        % declaration, we may print a comment before the Java type
        % (see type_to_string_for_java). This would yield nested comments,
        % which Java does not allow.
        indent_line_after_context(Stream, Info ^ joi_line_numbers,
            marker_comment, Context, Indent, !IO),
        FuncNameStr = function_name_to_string_for_java(FuncName),
        io.format(Stream, "// external: %s\n", [s(FuncNameStr)], !IO)
    ;
        MaybeBody = body_defined_here(_),
        IndentStr = indent2_string(Indent),
        maybe_output_pre_function_comment(Stream, Info ^ joi_auto_comments,
            IndentStr, "// ", "", FunctionDefn, !IO),
        indent_line_after_context(Stream, Info ^ joi_line_numbers,
            marker_comment, Context, Indent, !IO),
        output_function_decl_flags_for_java(Info, Stream, Flags, !IO),
        output_func_for_java(Info, Stream, Indent, FuncName, OutputAux,
            Context, Params, MaybeBody, !IO)
    ).

:- pred output_func_for_java(java_out_info::in, io.text_output_stream::in,
    indent::in, mlds_function_name::in, output_aux::in, prog_context::in,
    mlds_func_params::in, mlds_function_body::in, io::di, io::uo) is det.

output_func_for_java(Info, Stream, Indent, FuncName, OutputAux, Context,
        Signature, MaybeBody, !IO) :-
    output_func_decl_for_java(Info, Stream, Indent, FuncName, OutputAux,
        Signature, !IO),
    io.write_string(Stream, "\n", !IO),
    (
        MaybeBody = body_external
        % The signature above will be printed inside a comment.
    ;
        MaybeBody = body_defined_here(Body),
        FuncInfo = func_info_csj(Signature),
        % Do not place redundant brackets around a block.
        ( if Body = ml_stmt_block(_, _, _, _) then
            output_statement_for_java(Info, Stream, Indent, FuncInfo, Body,
                _ExitMethods, !IO)
        else
            IndentStr = indent2_string(Indent),
            io.format(Stream, "%s{\n", [s(IndentStr)], !IO),
            output_statement_for_java(Info, Stream, Indent + 1u, FuncInfo,
                Body, _ExitMethods, !IO),
            indent_line_after_context(Stream, Info ^ joi_line_numbers,
                marker_comment, Context, Indent, !IO),
            io.write_string(Stream, "}\n", !IO)
        )
    ).

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

:- pred output_function_decl_flags_for_java(java_out_info::in,
    io.text_output_stream::in, mlds_function_decl_flags::in,
    io::di, io::uo) is det.

output_function_decl_flags_for_java(Info, Stream, Flags, !IO) :-
    Flags = mlds_function_decl_flags(Access, PerInstance),
    output_access_for_java(Info, Stream, Access, !IO),
    output_per_instance_for_java(Stream, PerInstance, !IO).

:- pred output_access_for_java(java_out_info::in, io.text_output_stream::in,
    function_access::in, io::di, io::uo) is det.

output_access_for_java(_Info, Stream, Access, !IO) :-
    (
        Access = func_public,
        io.write_string(Stream, "public ", !IO)
    ;
        Access = func_private,
        io.write_string(Stream, "private ", !IO)
    ).

:- pred output_per_instance_for_java(io.text_output_stream::in,
    per_instance::in, io::di, io::uo) is det.

output_per_instance_for_java(Stream, PerInstance, !IO) :-
    (
        PerInstance = per_instance
    ;
        PerInstance = one_copy,
        io.write_string(Stream, "static ", !IO)
    ).

%---------------------------------------------------------------------------%
:- end_module ml_backend.mlds_to_java_func.
%---------------------------------------------------------------------------%
