%---------------------------------------------------------------------------%
% vim: ft=mercury ts=4 sw=4 et
%---------------------------------------------------------------------------%
% Copyright (C) 2025-2026 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.
%---------------------------------------------------------------------------%
%
% File: print_help.m.
% Main author: zs.

:- module libs.print_help.
:- interface.

:- import_module io.
:- import_module maybe.

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

:- type print_what_help
    --->    print_public_help
    ;       print_public_and_private_help.

    % Display short usage message.
    %
:- pred short_usage(io.text_output_stream::in, io::di, io::uo) is det.

    % Display long usage message for help.
    %
:- pred long_usage(io.text_output_stream::in, print_what_help::in,
    io::di, io::uo) is det.

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

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

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

:- pred list_optimization_options(io.text_output_stream::in, maybe(int)::in,
    io::di, io::uo) is det.

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

:- implementation.

:- import_module libs.optdb_help.
:- import_module libs.optimization_options.
:- import_module libs.option_categories.
:- import_module libs.options.

:- import_module bool.
:- import_module char.
:- import_module cord.
:- import_module getopt.
:- import_module int.
:- import_module list.
:- import_module map.
:- import_module one_or_more.
:- import_module one_or_more_map.
:- import_module require.
:- import_module set.
:- import_module solutions.
:- import_module string.

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

:- mutable(already_printed_usage, bool, no, ground,
    [untrailed, attach_to_io_state]).

short_usage(ProgressStream, !IO) :-
    % short_usage is called from many places; ensure that we don't print the
    % duplicate copies of the message.
    % XXX The above doesn't seem to be true anymore.
    get_already_printed_usage(AlreadyPrinted, !IO),
    (
        AlreadyPrinted = no,
        ShortUsageLines = [
            "Usage: mmc [<options>] <arguments>",
            "Use `mmc --help' for more information."
        ],
        write_lines(ProgressStream, ShortUsageLines, !IO),
        set_already_printed_usage(yes, !IO)
    ;
        AlreadyPrinted = yes
    ).

long_usage(ProgressStream, What, !IO) :-
    % long_usage is called from only one place, so can't print duplicate
    % copies of the long usage message. We can print both a short and along
    % usage message, but there is no simple way to avoid that.
    HeaderLines = [compiler_id_line | copyright_notice_lines] ++
        long_usage_header_lines,
    document_requested_options(help_plain_text, What,
        _SectionNames, OptionsLines),
    Lines = HeaderLines ++ OptionsLines,
    write_lines(ProgressStream, Lines, !IO).

document_options_for_users_guide(ProgressStream, !IO) :-
    document_requested_options(help_texinfo, print_public_and_private_help,
        MenuItemsTail, OptionsLines),
    MenuItems = [menu_item("Invocation overview", "") | MenuItemsTail],
    MenuLines = menu_items_to_menu(MenuItems),
    OverviewLines = string.split_at_char('\n', invocation_overview_section),
    % OverviewLines will start and end with blank lines.
    AllLines = MenuLines ++ OverviewLines ++ OptionsLines,
    write_lines(ProgressStream, AllLines, !IO).

write_copyright_notice(Stream, !IO) :-
    write_lines(Stream, copyright_notice_lines, !IO).

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

:- func compiler_id_line = string.

compiler_id_line = "Name: mmc - Melbourne Mercury Compiler".

:- func copyright_notice_lines = list(string).

copyright_notice_lines = [
    "Copyright (C) 1993-2012 The University of Melbourne",
    "Copyright (C) 2013-2026 The Mercury team"
].

:- func long_usage_header_lines = list(string).

long_usage_header_lines = [
    "Usage: mmc [<options>] <arguments>",
    "",
    "Arguments:",
    "    Arguments ending in `.m' are assumed to be source file names.",
    "    Arguments that do not end in `.m' are assumed to be module names.",
    "    Arguments of the form `@file' are replaced with " ++
        "the contents of the file.",
    "",
    "Options:"
].

:- func invocation_overview_section = string.

invocation_overview_section =
"
@node Invocation overview
@section Invocation overview
@findex --no-

@code{mmc} is invoked as
@example
mmc [@var{options}] @var{arguments}
@end example

Arguments can be either module names or file names.
Arguments ending in @samp{.m} are assumed to be file names,
while other arguments are assumed to be module names.
The compiler will convert module names to file names
by looking up the module name in the module-name-to-file-name map
in the @file{Mercury.modules} file it if exists.
(It can be created using a command such as @code{mmc -f *.m}.)
It @file{Mercury.modules} does not exist, then the compiler
will search for a module named e.g. @samp{foo.bar.baz}
in the files @file{foo.bar.baz.m}, @file{bar.baz.m}, and @file{baz.m},
in that order.

Options are either short (single-letter) options preceded by a single @samp{-},
or long options preceded by @samp{--}.
Options are case-sensitive.
We call options that do not take arguments @dfn{flags}.
Single-letter flags may be grouped with a single @samp{-}, e.g.@: @samp{-vVc}.
Single-letter flags may be negated
by appending another trailing @samp{-}, e.g.@: @samp{-v-}.
(You cannot both group @emph{and} negate single-letter flags at the same time.)
Long flags may be negated by preceding them with @samp{no-},
e.g.@: @samp{--no-verbose}.
".

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

:- type help_structure
    --->    help_atomic(
                help_atomic_group               :: help_option_group
            )
    ;       help_composite(
                help_composite_name             :: string,
                help_composite_menu_desc        :: string,
                help_composite_comment_lines    :: list(string),
                help_composite_parts            :: list(help_structure)
            ).

:- type help_option_group
    --->    help_option_group(
                hog_name                        :: string,
                hog_menu_desc                   :: string,
                hog_comment_lines               :: list(string),
                hog_categories                  :: list(option_category)
            ).

:- type section_depth
    --->    sd_section
    ;       sd_subsection
    ;       sd_subsubsection.

:- pred section_marker(section_depth::in, string::out) is det.

section_marker(Depth, Marker) :-
    ( Depth = sd_section,       Marker = "@section"
    ; Depth = sd_subsection,    Marker = "@subsection"
    ; Depth = sd_subsubsection, Marker = "@subsubsection"
    ).

:- pred next_depth(section_depth::in, section_depth::out) is det.

next_depth(Depth, SubDepth) :-
    ( Depth = sd_section,       SubDepth = sd_subsection
    ; Depth = sd_subsection,    SubDepth = sd_subsubsection
    ; Depth = sd_subsubsection, unexpected($pred, "sd_subsubsection")
    ).

:- pred plain_text_header_indent(section_depth::in, string::out) is det.

plain_text_header_indent(Depth, Indent) :-
    % Let section names start at the left margin, but
    % indent the names of smaller sections.
    ( Depth = sd_section,        Indent = ""
    ; Depth = sd_subsection,     Indent = "  "
    ; Depth = sd_subsubsection,  Indent = single_indent
    ).

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

:- func all_chapters = list(help_structure).

all_chapters = AllSections :-
    SectionHelp = help_atomic(
        help_option_group(
            "Help options",
            "", [], [oc_help])),

    SectionCmdLine = help_atomic(
        help_option_group(
            "Options for modifying the command line",
            "", [], [oc_cmdline])),

    SectionOpmode = help_atomic(
        help_option_group(
            "Options that give the compiler its overall task",
            "", [], [oc_opmode])),

    SubSectionGradeGen = help_atomic(help_option_group(
        "Grades and grade components",
        "Setting the compilation model", [], [oc_grade_gen])),
    SubSectionGradeTarget = help_atomic(help_option_group(
        "Target options",
        "Choosing the target language", [], [oc_grade_target])),
    SubSectionGradeLlds = help_atomic(help_option_group(
        "LLDS backend grade options",
        "For the low-level C backend", [], [oc_grade_llds])),
    SubSectionGradeMlds = help_atomic(help_option_group(
        "MLDS backend grade options",
        "For the high-level C/Java/C# backend", [], [oc_grade_mlds])),

    SubSubSectionGradeMdb = help_atomic(help_option_group(
        "Mdb debugging grade options",
        "", [], [oc_grade_mdb])),
    SubSubSectionGradeSsdb = help_atomic(help_option_group(
        "Ssdb debugging grade options",
        "", [], [oc_grade_ssdb])),
    SubSectionGradeDbg = help_composite("Debugging grade options",
        "", [],
        [SubSubSectionGradeMdb, SubSubSectionGradeSsdb]),

    SubSubSectionGradeMprof = help_atomic(help_option_group(
        "Mprof profiling grade options",
        "", [], [oc_grade_mprof])),
    SubSubSectionGradeMdprof = help_atomic(help_option_group(
        "Deep profiling grade options",
        "", [], [oc_grade_mdprof])),
    SubSubSectionGradeClprof = help_atomic(help_option_group(
        "Complexity profiling grade options",
        "", [], [oc_grade_clprof])),
    SubSubSectionGradeTsprof = help_atomic(help_option_group(
        "Threadscope profiling grade options",
        "", [], [oc_grade_tsprof])),
    SubSectionGradeProf = help_composite("Profiling grade options",
        "", [],
        [SubSubSectionGradeMprof, SubSubSectionGradeMdprof,
        SubSubSectionGradeClprof, SubSubSectionGradeTsprof]),

    SubSectionGradeEtc = help_atomic(help_option_group(
        "Optional feature grade options",
        "", [], [oc_grade_etc])),
    SubSectionGradeDev = help_atomic(help_option_group(
        "Developer grade options",
        "Not for general use", [], [oc_grade_dev])),
    SectionGrade = help_composite("Grade options",
        "", [],
        [SubSectionGradeGen, SubSectionGradeTarget,
        SubSectionGradeLlds, SubSectionGradeMlds,
        SubSectionGradeDbg, SubSectionGradeProf,
        SubSectionGradeEtc, SubSectionGradeDev]),

    SectionInfer = help_atomic(
        help_option_group(
            "Options that control inference",
            "", [], [oc_infer])),

    SectionSemantics = help_atomic(
        help_option_group(
            "Options specifying the intended semantics",
            "", [], [oc_semantics])),

    SectionVerbosity = help_atomic(
        help_option_group(
            "Verbosity options",
            "", [], [oc_verbosity])),

    SubSectionDiagGen = help_atomic(help_option_group(
        "Options that control diagnostics",
        "", [], [oc_diag_gen])),
    SubSectionDiagColor = help_atomic(help_option_group(
        "Options that control color in diagnostics",
        "", [], [oc_diag_color, oc_diag_int])),
    SectionDiag = help_composite("Diagnostics options",
        "", [],
        [SubSectionDiagGen, SubSectionDiagColor]),

    SubSubSectionWarnDodgyMod = help_atomic(help_option_group(
        "Warnings about possible module incorrectness",
        "", [], [oc_warn_dodgy_mod])),
    SubSubSectionWarnDodgyInst = help_atomic(help_option_group(
        "Warnings about possible inst incorrectness",
        "", [], [oc_warn_dodgy_inst])),
    SubSubSectionWarnDodgyPred = help_atomic(help_option_group(
        "Warnings about possible predicate incorrectness",
        "", [], [oc_warn_dodgy_pred])),
    SubSubSectionWarnDodgyPrg = help_atomic(help_option_group(
        "Warnings about possible pragma incorrectness",
        "", [], [oc_warn_dodgy_prg])),
    SubSubSectionWarnDodgyGoal = help_atomic(help_option_group(
        "Warnings about possible goal incorrectness",
        "", [], [oc_warn_dodgy_goal])),
    SubSubSectionWarnFile = help_atomic(help_option_group(
        "Warnings about missing files",
        "", [], [oc_warn_file])),
    SubSectionWarnDodgy = help_composite(
        "Warnings about possible incorrectness",
        "", [],
        [SubSubSectionWarnDodgyMod, SubSubSectionWarnDodgyInst,
        SubSubSectionWarnDodgyPred, SubSubSectionWarnDodgyPrg,
        SubSubSectionWarnDodgyGoal, SubSubSectionWarnFile]),

    SubSectionWarnPerf = help_atomic(help_option_group(
        "Warnings about possible performance issues",
        "", [], [oc_warn_perf, oc_warn_perf_c])),

    SubSubSectionWarnStyleMod = help_atomic(help_option_group(
        "Warnings about style issues with modules",
        "", [], [oc_warn_style_mod])),
    SubSubSectionWarnStylePred = help_atomic(help_option_group(
        "Warnings about style issues with predicates",
        "", [], [oc_warn_style_pred])),
    SubSubSectionWarnStyleGoal = help_atomic(help_option_group(
        "Warnings about style issues with goals",
        "", [], [oc_warn_style_goal, oc_warn_style_goal_c])),
    SubSubSectionWarnStyleOrder = help_atomic(help_option_group(
        "Warnings about missing order",
        "", [], [oc_warn_style_order])),
    SubSubSectionWarnStyleContig = help_atomic(help_option_group(
        "Warnings about missing contiguity",
        "", [], [oc_warn_style_ctg, oc_warn_style_ctg_c])),
    SubSectionWarnStyle = help_composite(
        "Warnings about programming style",
        "", [],
        [SubSubSectionWarnStyleMod, SubSubSectionWarnStylePred,
        SubSubSectionWarnStyleGoal, SubSubSectionWarnStyleOrder,
        SubSubSectionWarnStyleContig]),

    SubSubSectionWarnLangChange = help_atomic(help_option_group(
        "Warnings about language changes",
        "", [], [oc_warn_lang_change])),

    SubSectionWarnCtrl = help_atomic(help_option_group(
        "Options that control warnings",
        "", [], [oc_warn_ctrl])),
    SubSectionWarnHalt = help_atomic(help_option_group(
        "Options about halting for warnings",
        "", [], [oc_warn_halt])),
    SectionWarn = help_composite("Warning options",
        "", [],
        [SubSectionWarnDodgy, SubSectionWarnPerf, SubSectionWarnStyle,
        SubSubSectionWarnLangChange,
        SubSectionWarnCtrl, SubSectionWarnHalt]),

    % XXX Should these two chapters instead be two sections in one chapter?
    SectionInform = help_atomic(
        help_option_group("Options that request information",
        "", [], [oc_inform])),
    SectionFileReq = help_atomic(
        help_option_group("Options that ask for informational files",
        "", [], [oc_file_req])),

    % As of the time of its creation, this section contains only
    % private options.
    SectionReport = help_atomic(
        help_option_group("Options that control some compiler reports",
        "", [], [oc_report])),

    SectionTraceGoal = help_atomic(
        help_option_group("Controlling trace goals",
        "", [], [oc_tracegoal])),

    % Once ssdb debugging is publicly documented, we should replace these
    % two chapters (the second of which is not printed for non-developers)
    % with a single two-section chapter.
    SectionDebugMdb = help_atomic(
        help_option_group("Preparing code for mdb debugging",
        "", [], [oc_mdb, oc_mdb_dev])),
    SectionDebugSsdb = help_atomic(
        help_option_group("Preparing code for ssdb debugging",
        "", [], [oc_ssdb, oc_ssdb_dev])),

    SectionProfiling = help_atomic(
        help_option_group("Preparing code for mdprof profiling",
        "", [], [oc_mdprof])),

    SubSectionOptCtrl = help_atomic(help_option_group(
        "Overall control of optimizations",
        "", [], [oc_opt_ctrl])),
    SubSectionOptHH = help_atomic(help_option_group(
        "Source-to-source optimizations",
        "", [], [oc_opt_hh])),
    SubSectionOptHHE = help_atomic(help_option_group(
        "Experimental source-to-source optimizations",
        "", [], [oc_opt_hh_exp])),
    SubSectionOptHLM = help_atomic(help_option_group(
        "Optimizations during code generation",
        "", [], [oc_opt_hlm])),
    % XXX Should the categories here be separate sections?
    SubSectionOptMM = help_atomic(help_option_group(
        "Optimizations specific to high level code",
        "", [], [oc_opt_hm, oc_opt_mm])),
    % XXX Should the categories here be separate sections?
    SubSectionOptLL = help_atomic(help_option_group(
        "Optimizations specific to low level code",
        "", [], [oc_opt_hl, oc_opt_ll, oc_opt_lc])),
    SectionOpt = help_composite("Optimization options",
        "", [],
        [SubSectionOptCtrl, SubSectionOptHH, SubSectionOptHHE,
        SubSectionOptHLM, SubSectionOptMM, SubSectionOptLL]),

    SubSectionPlainOpt = help_atomic(
        help_option_group(
            "Non-transitive intermodule optimization",
            "", [], [oc_plain_opt])),
    SubSectionTransOpt = help_atomic(
        help_option_group(
            "Transitive intermodule optimization",
            "", [], [oc_trans_opt])),
    SubSectionLatexOpt = help_atomic(
        help_option_group(
            "Intermodule optimization to a fixpoint",
            "", [], [oc_latex_opt])),
    SectionInterOpt = help_composite("Intermodule optimization",
            "", [],
            [SubSectionPlainOpt, SubSectionTransOpt, SubSectionLatexOpt]),

    SubSectionPmTerm1 = help_atomic(help_option_group(
        "The termination analyser based on linear inequality constraints",
        "", [], [oc_pm_term1])),
    SubSectionPmTerm2 = help_atomic(help_option_group(
        "The termination analyser based on convex constraints",
        "", [], [oc_pm_term2])),
    SubSectionPmMisc = help_atomic(help_option_group(
        "Other program analyses",
        "", [], [oc_pm_misc])),
    SectionAnalysis = help_composite("Program analyses",
        "", [],
        [SubSectionPmTerm1, SubSectionPmTerm2, SubSectionPmMisc]),

    SectionModOutput = help_atomic(
        help_option_group(
            "Options that ask for modified output",
            "", [], [oc_output_mod, oc_output_dev])),

    SectionMmcMake = help_atomic(
        help_option_group(
            "Options for controlling mmc --make",
            "", [], [oc_make])),

    SubSectionCompileGen = help_atomic(help_option_group(
        "General options for compiling target language code",
        "", [], [oc_target_comp])),
    SubSectionCompileC = help_atomic(help_option_group(
        "Options for compiling C code",
        "", [], [oc_target_c])),
    SubSectionCompileJava = help_atomic(help_option_group(
        "Options for compiling Java code",
        "", [], [oc_target_java])),
    SubSectionCompileCsharp = help_atomic(help_option_group(
        "Options for compiling C# code",
        "", [], [oc_target_csharp])),
    SectionCompile = help_composite(
        "Options for target language compilation",
        "", [],
        [SubSectionCompileGen, SubSectionCompileC, SubSectionCompileJava,
        SubSectionCompileCsharp]),

    SubSectionLinkGen = help_atomic(help_option_group(
        "General options for linking",
        "", [], [oc_link_c_cs_j])),
    SubSectionLinkCCsharp = help_atomic(help_option_group(
        "Options for linking C or C# code",
        "", [], [oc_link_c_cs])),
    SubSectionLinkC = help_atomic(help_option_group(
        "Options for linking just C code",
        "", [], [oc_link_c])),
    SubSectionLinkCsharp = help_atomic(help_option_group(
        "Options for linking just C# code",
        "", [], [oc_link_csharp])),
    SubSectionLinkJava = help_atomic(help_option_group(
        "Options for linking just Java code",
        "", [], [oc_link_java])),
    SectionLink = help_composite(
        "Options for linking",
        "", [],
        [SubSectionLinkGen, SubSectionLinkCCsharp,
        SubSectionLinkC, SubSectionLinkJava, SubSectionLinkCsharp]),

    SectionFileSearch = help_atomic(
        help_option_group(
            "Options controlling searches for files",
            "", [], [oc_search])),

    SectionBuild = help_atomic(
        help_option_group(
            "Options controlling the library installation process",
            "", [], [oc_buildsys])),

    SectionEnv = help_atomic(
        help_option_group(
            "Options specifying properties of the environment",
            "", [], [oc_env])),

    SectionConfig = help_atomic(
        help_option_group(
            "Options that record autoconfigured parameters",
            "", [], [oc_config])),

    SectionMconfig = help_atomic(
        help_option_group(
            "Options reserved for Mercury.config files",
            "", [], [oc_mconfig])),

    SubSectionDevCtrl = help_atomic(help_option_group(
        "Operation selection options for developers only",
        "", [], [oc_dev_ctrl])),
    SubSectionDevVerb = help_atomic(help_option_group(
        "Verbosity options for developers only",
        "", [], [oc_dev_verb])),
    SubSectionDevDebug = help_atomic(help_option_group(
        "Options that can help debug the compiler",
        "", [], [oc_dev_debug])),
    SubSectionDevDump = help_atomic(help_option_group(
        "Dumping out internal compiler data structures",
        "", [], [oc_dev_dump])),
    SubSectionDevInternal = help_atomic(help_option_group(
        "Options intended for internal use by the compiler only",
        "", [], [oc_internal])),
    SectionDev = help_composite("Options for developers only",
        "", [],
        [SubSectionDevCtrl, SubSectionDevVerb, SubSectionDevDebug,
        SubSectionDevDump, SubSectionDevInternal]),

    SectionUnused = help_atomic(
        help_option_group(
            "Now-unused former options kept for compatibility",
            "", [], [oc_unused])),

    AllSections = [
        SectionHelp,
        SectionCmdLine,
        SectionOpmode,
        SectionGrade,
        SectionInfer,
        SectionSemantics,
        SectionVerbosity,

        SectionDiag,
        SectionWarn,
        SectionInform,
        SectionFileReq,
        SectionReport,

        SectionTraceGoal,
        SectionDebugMdb,
        SectionDebugSsdb,
        SectionProfiling,

        SectionOpt,
        SectionInterOpt,
        SectionAnalysis,

        SectionModOutput,
        SectionMmcMake,

        SectionCompile,
        SectionLink,
        SectionFileSearch,
        SectionBuild,

        SectionEnv,
        SectionConfig,
        SectionMconfig,

        SectionDev,
        SectionUnused
    ].

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

:- type help_format
    --->    help_plain_text
    ;       help_texinfo.

:- inst help_plain_text for help_format/0
    --->    help_plain_text.
:- inst help_texinfo for help_format/0
    --->    help_texinfo.

:- pred document_requested_options(help_format, print_what_help,
    list(menu_item), list(string)).
:- mode document_requested_options(in(help_plain_text), in, out, out) is det.
:- mode document_requested_options(in(help_texinfo), in, out, out) is det.

document_requested_options(Format, What, SectionNames, OptionsLines) :-
    bool_option_initial_n_y(InitialNoOptions, InitialYesOptions),
    map.init(InitialValueMap0),
    list.foldl(insert_initial(no), InitialNoOptions,
        InitialValueMap0, InitialValueMap1),
    list.foldl(insert_initial(yes), InitialYesOptions,
        InitialValueMap1, InitialValueMap),

    list.foldl(build_set_at_opt_level_map, [0, 1, 2, 3, 4, 5, 6],
        map.init, SetAtOptLevelMap),

    OptionMaps = option_maps(InitialValueMap, SetAtOptLevelMap),

    % We check whether we have covered all possible option categories
    %
    % - getting a set containing all of those categories;
    % - deleting each category from the set as it is handled; and then
    % - checking that what remains is the empty set.
    CategoryPred =
        ( pred(Cat::out) is multi :-
            option_categories(Cat, _)
        ),
    solutions_set(CategoryPred, AllCategoriesSet),
    acc_help_structures(OptionMaps, Format, What, sd_section, all_chapters,
        AllCategoriesSet, UndoneCategoriesSet,
        cord.init, SectionNameCord, cord.init, OptionsLineCord,
        0, _NumDocOpts),
    set.to_sorted_list(UndoneCategoriesSet, UndoneCategories),
    (
        UndoneCategories = []
    ;
        UndoneCategories = [_ | _],
        unexpected($pred, "undone: " ++ string(UndoneCategories))
    ),
    SectionNames = cord.list(SectionNameCord),
    OptionsLines = cord.list(OptionsLineCord).

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

:- type option_maps
    --->    option_maps(
                initial_bool_value_map,
                set_at_opt_level_map
            ).

    % Maps each bool option handled by optimization_options.m
    % to its initial value.
:- type initial_bool_value_map == map(option, bool).

:- type set_at_opt_level_map == one_or_more_map(option, set_at_opt_level).

:- type set_at_opt_level
    --->    set_at_opt_level(
                % At this optimization level, this option is ...
                int,

                % ... set to this value, which must be a bool or an int.
                option_data_bool_int
            ).

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

:- pred insert_initial(bool::in, option::in,
    initial_bool_value_map::in, initial_bool_value_map::out) is det.

insert_initial(InitialValue, Option, !InitialValueMap) :-
    map.det_insert(Option, InitialValue, !InitialValueMap).

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

:- pred build_set_at_opt_level_map(int::in,
    set_at_opt_level_map::in, set_at_opt_level_map::out) is det.

build_set_at_opt_level_map(Level, !SetAtOptLevelMap) :-
    ( if opts_enabled_at_level(Level, _LevelDesc, DocOpts) then
        list.foldl(record_option_at_opt_level_map(Level), DocOpts,
            !SetAtOptLevelMap)
    else
        true
    ).

:- pred record_option_at_opt_level_map(int::in,
    documented_optimization_option::in,
    set_at_opt_level_map::in, set_at_opt_level_map::out) is det.

record_option_at_opt_level_map(Level, DocOpt, !SetAtOptLevelMap) :-
    DocOpt = doc_oo(_, Option, OptionData),
    SetAtOptLevel = set_at_opt_level(Level, OptionData),
    one_or_more_map.add(Option, SetAtOptLevel, !SetAtOptLevelMap).

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

:- type menu_item
    --->    menu_item(string, string).
            % The name of the menu item, and its short description, if any.
            % (Nonexistent descriptions are represented by an empty string.)

:- pred acc_help_structures(option_maps, help_format, print_what_help,
    section_depth, list(help_structure),
    set(option_category), set(option_category),
    cord(menu_item), cord(menu_item), cord(string), cord(string), int, int).
:- mode acc_help_structures(in, in(help_plain_text), in, in, in,
    in, out, in, out, in, out, in, out) is det.
:- mode acc_help_structures(in, in(help_texinfo), in, in, in,
    in, out, in, out, in, out, in, out) is det.

acc_help_structures(_, _, _, _, [],
        !Categories, !MenuItemCord, !LineCord, !NumDocOpts).
acc_help_structures(OptionMaps, Format, What, Depth, [Structure | Structures],
        !Categories, !MenuItemCord, !LineCord, !NumDocOpts) :-
    acc_help_structure(OptionMaps, Format, What, Depth, Structure,
        !Categories, !MenuItemCord, !LineCord, !NumDocOpts),
    acc_help_structures(OptionMaps, Format, What, Depth, Structures,
        !Categories, !MenuItemCord, !LineCord, !NumDocOpts).

:- pred acc_help_structure(option_maps, help_format, print_what_help,
    section_depth, help_structure, set(option_category), set(option_category),
    cord(menu_item), cord(menu_item), cord(string), cord(string), int, int).
:- mode acc_help_structure(in, in(help_plain_text), in, in, in,
    in, out, in, out, in, out, in, out) is det.
:- mode acc_help_structure(in, in(help_texinfo), in, in, in,
    in, out, in, out, in, out, in, out) is det.

acc_help_structure(OptionMaps, Format, What, Depth, Structure,
        !Categories, !MenuItemCord, !LineCord, !NumDocOpts) :-
    (
        Structure = help_atomic(SubSection),
        SubSection = help_option_group(GroupName, MenuDesc, _, _),
        acc_help_option_group(OptionMaps, Format, What, Depth,
            SubSection, !Categories, cord.init, _MenuItemCord,
            !LineCord, 0, SubNumDocOpts),
        !:NumDocOpts = !.NumDocOpts + SubNumDocOpts,
        ( if SubNumDocOpts > 0 then
            cord.snoc(menu_item(GroupName, MenuDesc), !MenuItemCord)
        else
            true
        )
    ;
        Structure = help_composite(StructureName, StructureDesc,
            CommentLines, SubStructures),
        next_depth(Depth, SubDepth),
        acc_help_structures(OptionMaps, Format, What, SubDepth, SubStructures,
            !Categories, cord.init, SubMenuItemCord,
            cord.init, SubStructuresLineCord, 0, SubNumDocOpts),
        !:NumDocOpts = !.NumDocOpts + SubNumDocOpts,
        (
            Format = help_plain_text,
            ( if SubNumDocOpts = 0 then
                true
            else
                plain_text_header_indent(Depth, NameIndent),
                some [!GroupLineCord]
                (
                    !:GroupLineCord = cord.init,
                    cord.snoc("", !GroupLineCord),
                    cord.snoc(NameIndent ++ StructureName, !GroupLineCord),
                    (
                        CommentLines = []
                    ;
                        CommentLines = [_ | _],
                        cord.snoc("", !GroupLineCord),
                        !:GroupLineCord = !.GroupLineCord ++
                            cord.from_list(CommentLines)
                    ),
                    !:GroupLineCord = !.GroupLineCord ++ SubStructuresLineCord,
                    !:LineCord = !.LineCord ++ !.GroupLineCord
                )
            )
        ;
            Format = help_texinfo,
            some [!GroupLineCord]
            (
                !:GroupLineCord = cord.init,
                section_marker(Depth, SectionKind),
                cord.snoc("", !GroupLineCord),
                add_node_line("@node",     StructureName, !GroupLineCord),
                add_node_line(SectionKind, StructureName, !GroupLineCord),
                add_node_line("@cindex",   StructureName, !GroupLineCord),
                cord.snoc("", !GroupLineCord),
                SubMenuLines = menu_items_to_menu(cord.list(SubMenuItemCord)),
                !:GroupLineCord = !.GroupLineCord ++
                    cord.from_list(SubMenuLines),
                (
                    CommentLines = []
                ;
                    CommentLines = [_ | _],
                    cord.snoc("", !GroupLineCord),
                    !:GroupLineCord = !.GroupLineCord ++
                        cord.from_list(CommentLines)
                ),
                % If we did not gather any non-commented-out option help texts,
                % then comment out the group header as well.
                %
                % Include a menu item for this section only if the section
                % has *some* non-commented-out parts.
                ( if SubNumDocOpts = 0 then
                    comment_out_texinfo_lines(!GroupLineCord)
                else
                    cord.snoc(menu_item(StructureName, StructureDesc),
                        !MenuItemCord)
                ),
                !:GroupLineCord = !.GroupLineCord ++ SubStructuresLineCord,
                !:LineCord = !.LineCord ++ !.GroupLineCord
            )
        )
    ).

:- pred acc_help_option_group(option_maps, help_format,
    print_what_help, section_depth, help_option_group,
    set(option_category), set(option_category),
    cord(menu_item), cord(menu_item), cord(string), cord(string), int, int).
:- mode acc_help_option_group(in, in(help_plain_text), in, in, in,
    in, out, in, out, in, out, in, out) is det.
:- mode acc_help_option_group(in, in(help_texinfo), in, in, in,
    in, out, in, out, in, out, in, out) is det.

acc_help_option_group(OptionMaps, Format, What, Depth, Group,
        !Categories, !MenuItemCord, !LineCord, !NumDocOpts) :-
    Group = help_option_group(GroupName, MenuDesc, CommentLines, Categories),
    ( if set.remove_list(Categories, !Categories) then
        true
    else
        string.format("some category in %s is listed more than once",
            [s(string(Categories))], Msg),
        unexpected($pred, Msg)
    ),
    list.map(get_optdb_records_in_category, Categories, OptdbRecordSets),
    OptdbRecordSet = set.union_list(OptdbRecordSets),

    acc_help_messages(OptionMaps, Format, What,
        set.to_sorted_list(OptdbRecordSet),
        cord.init, HelpTextLinesCord, 0, GroupNumDocOpts),
    !:NumDocOpts = !.NumDocOpts + GroupNumDocOpts,
    (
        Format = help_plain_text,
        ( if GroupNumDocOpts = 0 then
            true
        else
            plain_text_header_indent(Depth, NameIndent),
            some [!GroupLineCord]
            (
                !:GroupLineCord = cord.init,
                cord.snoc("", !GroupLineCord),
                cord.snoc(NameIndent ++ GroupName, !GroupLineCord),
                (
                    CommentLines = []
                ;
                    CommentLines = [_ | _],
                    cord.snoc("", !GroupLineCord),
                    list.foldl(acc_prefixed_line(NameIndent),
                        CommentLines, !GroupLineCord)
                ),
                !:GroupLineCord = !.GroupLineCord ++ HelpTextLinesCord,
                !:LineCord = !.LineCord ++ !.GroupLineCord
            )
        )
    ;
        Format = help_texinfo,
        some [!GroupStartLineCord, !GroupEndLineCord]
        (
            !:GroupStartLineCord = cord.init,
            !:GroupEndLineCord = cord.init,
            section_marker(Depth, SectionKind),
            cord.snoc("", !GroupStartLineCord),
            add_node_line("@node",     GroupName, !GroupStartLineCord),
            add_node_line(SectionKind, GroupName, !GroupStartLineCord),
            add_node_line("@cindex",   GroupName, !GroupStartLineCord),
            (
                CommentLines = []
            ;
                CommentLines = [_ | _],
                cord.snoc("", !GroupStartLineCord),
                !:GroupStartLineCord = !.GroupStartLineCord ++
                    cord.from_list(CommentLines)
            ),
            cord.snoc("", !GroupStartLineCord),
            cord.snoc("@table @asis", !GroupStartLineCord),

            cord.snoc("", !GroupEndLineCord),
            cord.snoc("@end table", !GroupEndLineCord),

            % If we did not gather any non-commented-out option help texts,
            % then comment out the group header as well.
            ( if GroupNumDocOpts = 0 then
                !:GroupStartLineCord = cord.map(comment_out_texinfo_line,
                    !.GroupStartLineCord),
                !:GroupEndLineCord = cord.map(comment_out_texinfo_line,
                    !.GroupEndLineCord)
            else
                cord.snoc(menu_item(GroupName, MenuDesc), !MenuItemCord)
            ),
            GroupLineCord = !.GroupStartLineCord ++ HelpTextLinesCord ++
                !.GroupEndLineCord,
            !:LineCord = !.LineCord ++ GroupLineCord
        )
    ).

:- pred get_optdb_records_in_category(option_category::in,
    set(optdb_record)::out) is det.

get_optdb_records_in_category(Cat, OptdbRecordSet) :-
    OptdbPred =
        ( pred(OptdbRecord::out) is multi :-
            optdb(Cat, Opt, OptData, Help),
            OptdbRecord = optdb_record(Opt, Cat, OptData, Help)
        ),
    solutions_set(OptdbPred, OptdbRecordSet).

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

:- type optdb_record
    --->    optdb_record(
                % Put the option first, so that we can use the order
                % of options in the option type to control the relative
                % ordering of options in the help text even if they are
                % in nominally-different option categories (such as
                % oc_warn_style vs oc_warn_style_c).
                option,
                option_category,
                option_data,
                libs.optdb_help.help
            ).

:- type maybe_expect_arg
    --->    do_not_expect_arg
    ;       expect_arg.

:- type maybe_negate
    --->    do_not_negate
    ;       negate.

:- type maybe_add_negative
    --->    no_negative_version
    ;       add_negative_version.

:- type index_versions
    --->    index_positive_only
    ;       index_negative_only
    ;       index_positive_and_negative.

:- type option_params
    --->    option_params(
                op_expect_arg           :: maybe_expect_arg,
                op_negate               :: maybe_negate,
                op_add_negative_opt     :: maybe_add_negative,
                op_index_versions       :: index_versions
            ).

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

:- pred get_optdb_record_params(option_maps::in, optdb_record::in,
    option_params::out) is det.

get_optdb_record_params(OptionMaps, OptdbRecord, Params) :-
    OptdbRecord = optdb_record(Option, _Cat, OptionData, _Help),
    (
        OptionData = bool(Bool),
        MaybeExpectArg = do_not_expect_arg,
        (
            Bool = no,
            MaybeNegate = do_not_negate,
            % The negative version is the default, so the absence
            % of the option is just as good as its negation.
            % We do not want to create index entries for e.g.
            % --no-help, --no-make-int, etc.
            IndexVersions = index_positive_only
        ;
            Bool = yes,
            MaybeNegate = negate,
            % The negative version is the default, so the absence
            % of the option is just as good as its positive version.
            % Nevertheless, if we have an index entry for -no-xyz,
            % it would look strange to have no index entry for --xyz.
            IndexVersions = index_positive_and_negative
        ),
        MaybeAddNegVersionOpt = no_negative_version
    ;
        OptionData = bool_special,
        MaybeExpectArg = do_not_expect_arg,
        OptionMaps = option_maps(InitialValueMap, _),
        ( if map.search(InitialValueMap, Option, InitialBool) then
            (
                InitialBool = no,
                MaybeNegate = do_not_negate,
                IndexVersions = index_positive_only
            ;
                InitialBool = yes,
                MaybeNegate = negate,
                IndexVersions = index_positive_and_negative
            )
        else
            MaybeNegate = do_not_negate,
            IndexVersions = index_positive_and_negative
        ),
        MaybeAddNegVersionOpt = no_negative_version
    ;
        ( OptionData = int(_)
        ; OptionData = string(_)
        ; OptionData = int_special
        ; OptionData = string_special
        ),
        MaybeExpectArg = expect_arg,
        MaybeNegate = do_not_negate,
        MaybeAddNegVersionOpt = no_negative_version,
        IndexVersions = index_positive_only
    ;
        OptionData = special,
        MaybeExpectArg = do_not_expect_arg,
        MaybeNegate = do_not_negate,
        MaybeAddNegVersionOpt = no_negative_version,
        IndexVersions = index_positive_only
    ;
        OptionData = file_special,
        MaybeExpectArg = expect_arg,
        MaybeNegate = do_not_negate,
        MaybeAddNegVersionOpt = no_negative_version,
        IndexVersions = index_positive_only
    ;
        ( OptionData = accumulating(_)
        ; OptionData = maybe_int(_)
        ; OptionData = maybe_string(_)
        ; OptionData = maybe_string_special
        ),
        MaybeExpectArg = expect_arg,
        MaybeNegate = do_not_negate,
        MaybeAddNegVersionOpt = add_negative_version,
        IndexVersions = index_positive_and_negative
    ),
    Params = option_params(MaybeExpectArg, MaybeNegate,
        MaybeAddNegVersionOpt, IndexVersions).

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

:- pred acc_help_messages(option_maps, help_format, print_what_help,
    list(optdb_record), cord(string), cord(string), int, int).
:- mode acc_help_messages(in, in(help_plain_text), in, in, in, out, in, out)
    is det.
:- mode acc_help_messages(in, in(help_texinfo), in, in, in, out, in, out)
    is det.

acc_help_messages(_, _, _, [], !EffectiveLinesCord, !NumDocOpts).
acc_help_messages(OptionMaps, Format, What, [OptdbRecord | OptdbRecords],
        !EffectiveLinesCord, !NumDocOpts) :-
    (
        Format = help_plain_text,
        acc_help_message_plain(OptionMaps, What, OptdbRecord,
            !EffectiveLinesCord, !NumDocOpts)
    ;
        Format = help_texinfo,
        % For the user guide, we always add documentation for every option,
        % though private ones are commented out.
        acc_help_message_texinfo(OptionMaps, OptdbRecord,
            !EffectiveLinesCord, !NumDocOpts)
    ),
    acc_help_messages(OptionMaps, Format, What, OptdbRecords,
        !EffectiveLinesCord, !NumDocOpts).

:- pred acc_help_message_plain(option_maps::in, print_what_help::in,
    optdb_record::in,
    cord(string)::in, cord(string)::out, int::in, int::out) is det.

acc_help_message_plain(OptionMaps, What, OptdbRecord,
        !EffectiveLinesCord, !NumDocOpts) :-
    get_optdb_record_params(OptionMaps, OptdbRecord, Params),
    OptdbRecord = optdb_record(Option, _Cat, OptionData, Help),
    some [!LineCord]
    (
        !:LineCord = cord.init,
        (
            Help = no_help,
            PublicOrPrivate = help_private,
            DescPieces = []
        ;
            Help = unnamed_help(DescPieces),
            PublicOrPrivate = help_private,
            string.format("%sUNNAMED OPTION %s",
                [s(single_indent), s(string(Option))], NameLine),
            cord.snoc(NameLine, !LineCord)
        ;
            Help = gen_help(ShortNames, LongName, AltLongNames,
                PublicOrPrivate, DescPieces),
            acc_short_option_names_plain(Params, Option, no_arg, no_align,
                ShortNames, !LineCord),
            acc_long_option_name_plain(Params, Option, no_arg, no_align,
                LongName, !LineCord),
            acc_long_option_names_plain(Params, Option, no_arg, no_align,
                AltLongNames, !LineCord)
        ;
            (
                Help = help(LongName, DescPieces),
                MaybeArg = no_arg,
                PublicOrPrivate = help_public
            ;
                Help = arg_help(LongName, ArgName, DescPieces),
                MaybeArg = arg_name(ArgName),
                PublicOrPrivate = help_public
            ;
                Help = priv_help(LongName, DescPieces),
                MaybeArg = no_arg,
                PublicOrPrivate = help_private
            ;
                Help = priv_arg_help(LongName, ArgName, DescPieces),
                MaybeArg = arg_name(ArgName),
                PublicOrPrivate = help_private
            ),
            acc_long_option_name_plain(Params, Option, MaybeArg, no_align,
                LongName, !LineCord)
        ;
            (
                Help = alt_help(LongName, AltLongNames, DescPieces),
                MaybeArg = no_arg,
                PublicOrPrivate = help_public
            ;
                Help = alt_arg_help(LongName, AltLongNames, ArgName,
                    DescPieces),
                MaybeArg = arg_name(ArgName),
                PublicOrPrivate = help_public
            ;
                Help = priv_alt_help(LongName, AltLongNames, DescPieces),
                MaybeArg = no_arg,
                PublicOrPrivate = help_private
            ;
                Help = priv_alt_arg_help(LongName, AltLongNames, ArgName,
                    DescPieces),
                MaybeArg = arg_name(ArgName),
                PublicOrPrivate = help_private
            ),
            acc_long_option_name_plain(Params, Option, MaybeArg, no_align,
                LongName, !LineCord),
            acc_long_option_names_plain(Params, Option, MaybeArg, no_align,
                AltLongNames, !LineCord)
        ;
            (
                Help = short_help(ShortName, LongName, AltLongNames,
                    DescPieces),
                MaybeArg = no_arg,
                PublicOrPrivate = help_public
            ;
                Help = short_arg_help(ShortName, LongName, AltLongNames,
                    ArgName, DescPieces),
                MaybeArg = arg_name(ArgName),
                PublicOrPrivate = help_public
            ;
                Help = priv_short_help(ShortName, LongName, AltLongNames,
                    DescPieces),
                MaybeArg = no_arg,
                PublicOrPrivate = help_private
            ;
                Help = priv_short_arg_help(ShortName, LongName, AltLongNames,
                    ArgName, DescPieces),
                MaybeArg = arg_name(ArgName),
                PublicOrPrivate = help_private
            ),
            acc_short_option_name_plain(Params, Option, MaybeArg, no_align,
                ShortName, !LineCord),
            acc_long_option_name_plain(Params, Option, MaybeArg, no_align,
                LongName, !LineCord),
            acc_long_option_names_plain(Params, Option, MaybeArg, no_align,
                AltLongNames, !LineCord)
        ;
            (
                Help = alt_align_help(LongName, AltLongNames,
                    AlignedText, _, DescPieces),
                PublicOrPrivate = help_public
            ;
                Help = priv_alt_align_help(LongName, AltLongNames,
                    AlignedText, _, DescPieces),
                PublicOrPrivate = help_private
            ),
            MaybeArg = no_arg,
            Align = aligned_text(AlignedText),
            acc_long_option_name_plain(Params, Option, MaybeArg, Align,
                LongName, !LineCord),
            % The aligned text is added only to the first option name line.
            acc_long_option_names_plain(Params, Option, MaybeArg, no_align,
                AltLongNames, !LineCord)
        ;
            Help = short_alt_align_help(ShortName, LongName, AltLongNames,
                AlignedText, _, DescPieces),
            PublicOrPrivate = help_public,
            acc_short_option_name_plain(Params, Option, no_arg,
                aligned_text(AlignedText), ShortName, !LineCord),
            % The aligned text is added only to the first option name line.
            acc_long_option_name_plain(Params, Option, no_arg, no_align,
                LongName, !LineCord),
            acc_long_option_names_plain(Params, Option, no_arg, no_align,
                AltLongNames, !LineCord)
        ;
            Help = no_align_help(LongName, AlignedText, NoAlignedText,
                _, _, DescPieces),
            PublicOrPrivate = help_public,
            expect(is_bool(OptionData), $pred,
                "unexpected use of no_align_help"),
            ParamsNN = Params ^ op_negate := do_not_negate,
            FirstLine0 = long_option_name_line_plain(ParamsNN, Option,
                no_arg, LongName),
            SecondLine0 = long_negated_option_name_line_plain(LongName),
            % In this case, we add *different* aligned text to each line.
            add_aligned_text(AlignedText, FirstLine0, FirstLine),
            add_aligned_text(NoAlignedText, SecondLine0, SecondLine),
            cord.snoc(FirstLine, !LineCord),
            cord.snoc(SecondLine, !LineCord)
        ;
            Help = alt_arg_align_help(LongName, ArgAligns, DescPieces),
            PublicOrPrivate = help_public,
            % In this case, we add *different* aligned text to each line.
            list.foldl(acc_arg_align_text_plain(Params, Option, LongName),
                ArgAligns, !LineCord)
        ),
        ( if
            (
                PublicOrPrivate = help_public
            ;
                PublicOrPrivate = help_private,
                What = print_public_and_private_help
            )
        then
            !:NumDocOpts = !.NumDocOpts + 1,
            ( if
                cord.is_empty(!.LineCord),
                DescPieces = []
            then
                true
            else
                DescPrefix = double_indent,
                (
                    DescPieces = [],
                    EffDescPieces =
                        [w("There is no help text available.")]
                ;
                    DescPieces = [_ | _],
                    EffDescPieces = DescPieces
                ),
                % ZZZ 71
                reflow_lines(help_plain_text, 71, EffDescPieces,
                    _CindexTopics, _FindexTopics, ReflowLines),
                BlankLineCord = cord.singleton(""),
                list.foldl(acc_prefixed_line(DescPrefix), ReflowLines,
                    !LineCord),
                (
                    PublicOrPrivate = help_public,
                    PrivatePrefixCord = cord.init
                ;
                    PublicOrPrivate = help_private,
                    PrivatePrefixCord =
                        cord.singleton(single_indent ++ "PRIVATE OPTION")
                ),
                !:EffectiveLinesCord = !.EffectiveLinesCord ++
                    BlankLineCord ++ PrivatePrefixCord ++ !.LineCord
            )
        else
            true
        )
    ).

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

:- pred acc_help_message_texinfo(option_maps::in, optdb_record::in,
    cord(string)::in, cord(string)::out, int::in, int::out) is det.

acc_help_message_texinfo(OptionMaps, OptdbRecord,
        !EffectiveLinesCord, !NumDocOpts) :-
    get_optdb_record_params(OptionMaps, OptdbRecord, Params),
    OptdbRecord = optdb_record(Option, _Cat, OptionData, Help),
    some [!LineCord, !OptLineCord, !IndexLineCord]
    (
        !:OptLineCord = cord.init,
        !:IndexLineCord = cord.init,
        (
            Help = no_help,
            PublicOrPrivate = help_private,
            string.format("NO_HELP OPTION %s", [s(string(Option))], NameLine),
            cord.snoc(NameLine, !OptLineCord),
            DescPieces = []
        ;
            Help = unnamed_help(DescPieces),
            PublicOrPrivate = help_private,
            string.format("UNNAMED OPTION %s", [s(string(Option))], NameLine),
            cord.snoc(NameLine, !OptLineCord)
        ;
            Help = gen_help(ShortNames, LongName, AltLongNames,
                PublicOrPrivate, DescPieces),
            acc_short_option_names_texinfo(Params, Option, no_arg, no_align,
                ShortNames, !OptLineCord, !IndexLineCord),
            acc_long_option_name_texinfo(Params, Option, no_arg, no_align,
                LongName, !OptLineCord, !IndexLineCord),
            acc_long_option_names_texinfo(Params, Option, no_arg, no_align,
                AltLongNames, !OptLineCord, !IndexLineCord)
        ;
            (
                Help = help(LongName, DescPieces),
                MaybeArg = no_arg,
                PublicOrPrivate = help_public
            ;
                Help = arg_help(LongName, ArgName, DescPieces),
                MaybeArg = arg_name(ArgName),
                PublicOrPrivate = help_public
            ;
                Help = priv_help(LongName, DescPieces),
                MaybeArg = no_arg,
                PublicOrPrivate = help_private
            ;
                Help = priv_arg_help(LongName, ArgName, DescPieces),
                MaybeArg = arg_name(ArgName),
                PublicOrPrivate = help_private
            ),
            acc_long_option_name_texinfo(Params, Option, MaybeArg, no_align,
                LongName, !OptLineCord, !IndexLineCord)
        ;
            (
                Help = alt_help(LongName, AltLongNames, DescPieces),
                MaybeArg = no_arg,
                PublicOrPrivate = help_public
            ;
                Help = alt_arg_help(LongName, AltLongNames, ArgName,
                    DescPieces),
                MaybeArg = arg_name(ArgName),
                PublicOrPrivate = help_public
            ;
                Help = priv_alt_help(LongName, AltLongNames, DescPieces),
                MaybeArg = no_arg,
                PublicOrPrivate = help_private
            ;
                Help = priv_alt_arg_help(LongName, AltLongNames, ArgName,
                    DescPieces),
                MaybeArg = arg_name(ArgName),
                PublicOrPrivate = help_private
            ),
            acc_long_option_name_texinfo(Params, Option, MaybeArg, no_align,
                LongName, !OptLineCord, !IndexLineCord),
            acc_long_option_names_texinfo(Params, Option, MaybeArg, no_align,
                AltLongNames, !OptLineCord, !IndexLineCord)
        ;
            (
                Help = short_help(ShortName, LongName, AltLongNames,
                    DescPieces),
                MaybeArg = no_arg,
                PublicOrPrivate = help_public
            ;
                Help = short_arg_help(ShortName, LongName, AltLongNames,
                    ArgName, DescPieces),
                MaybeArg = arg_name(ArgName),
                PublicOrPrivate = help_public
            ;
                Help = priv_short_help(ShortName, LongName, AltLongNames,
                    DescPieces),
                MaybeArg = no_arg,
                PublicOrPrivate = help_private
            ;
                Help = priv_short_arg_help(ShortName, LongName, AltLongNames,
                    ArgName, DescPieces),
                MaybeArg = arg_name(ArgName),
                PublicOrPrivate = help_private
            ),
            acc_short_option_name_texinfo(Params, Option, MaybeArg, no_align,
                ShortName, !OptLineCord, !IndexLineCord),
            acc_long_option_name_texinfo(Params, Option, MaybeArg, no_align,
                LongName, !OptLineCord, !IndexLineCord),
            acc_long_option_names_texinfo(Params, Option, MaybeArg, no_align,
                AltLongNames, !OptLineCord, !IndexLineCord)
        ;
            (
                Help = alt_align_help(LongName, AltLongNames,
                    _, AlignedText, DescPieces),
                PublicOrPrivate = help_public
            ;
                Help = priv_alt_align_help(LongName, AltLongNames,
                    _, AlignedText, DescPieces),
                PublicOrPrivate = help_private
            ),
            MaybeArg = no_arg,
            Align = aligned_text(AlignedText),
            acc_long_option_name_texinfo(Params, Option, MaybeArg, Align,
                LongName, !OptLineCord, !IndexLineCord),
            % The aligned text is added only to the first option name line.
            acc_long_option_names_texinfo(Params, Option, MaybeArg, no_align,
                AltLongNames, !OptLineCord, !IndexLineCord)
        ;
            Help = short_alt_align_help(ShortName, LongName, AltLongNames,
                _, AlignedText, DescPieces),
            PublicOrPrivate = help_public,
            acc_short_option_name_texinfo(Params, Option, no_arg,
                aligned_text(AlignedText), ShortName,
                !OptLineCord, !IndexLineCord),
            % The aligned text is added only to the first option name line.
            acc_long_option_name_texinfo(Params, Option, no_arg, no_align,
                LongName, !OptLineCord, !IndexLineCord),
            acc_long_option_names_texinfo(Params, Option, no_arg, no_align,
                AltLongNames, !OptLineCord, !IndexLineCord)
        ;
            Help = no_align_help(LongName, _, _, AlignedText, NoAlignedText,
                DescPieces),
            PublicOrPrivate = help_public,
            expect(is_bool(OptionData), $pred,
                "unexpected use of no_align_help"),
            ParamsNN = Params ^ op_negate := do_not_negate,
            long_option_name_lines_texinfo(ParamsNN, Option,
                no_arg, LongName, FirstOptLine0, FirstIndexLine),
            SecondIndexLine = long_negated_option_name_texinfo(LongName),
            string.format("@code{%s}", [s(SecondIndexLine)], SecondOptLine0),
            % In this case, we add *different* aligned text to each line.
            FirstOptLine = FirstOptLine0 ++ " " ++ AlignedText,
            SecondOptLine = SecondOptLine0 ++ " " ++ NoAlignedText,
            add_option_line_texinfo(FirstOptLine, !OptLineCord),
            add_option_line_texinfo(SecondOptLine, !OptLineCord),
            add_findex_line_texinfo(FirstIndexLine, !IndexLineCord),
            add_findex_line_texinfo(SecondIndexLine, !IndexLineCord)
        ;
            Help = alt_arg_align_help(LongName, ArgAligns, DescPieces),
            PublicOrPrivate = help_public,
            % In this case, we add *different* aligned text to each line.
            list.foldl2(acc_arg_align_text_texinfo(Params, Option, LongName),
                ArgAligns, !OptLineCord, !IndexLineCord),
            % The option lines each contain both the option name and
            % one of different arg names, but the index lines contain only
            % the option name. We therefore need to get rid of duplicates.
            AAIndexLines0 = cord.list(!.IndexLineCord),
            list.sort_and_remove_dups(AAIndexLines0, AAIndexLines),
            !:IndexLineCord = cord.from_list(AAIndexLines)
        ),
        (
            DescPieces = [],
            EffDescPieces0 = [w("There is no help text available.")]
        ;
            DescPieces = [_ | _],
            EffDescPieces0 = DescPieces
        ),
        OptionMaps = option_maps(_, SetAtOptLevelMap),
        ( if
            one_or_more_map.search(SetAtOptLevelMap, Option,
                OoMSetAtOptLevels),
            get_main_long_name(Option, yes(OptionLongName))
        then
            SetAtOptLevels = one_or_more_to_list(OoMSetAtOptLevels),
            list.sort(SetAtOptLevels, SortedSetAtOptLevels),
            list.reverse(SortedSetAtOptLevels, RevSortedSetAtOptLevels),
            % 7 is the nonexistent "next level" after the highest *actual*
            % optimization level, which is -O6.
            acc_set_at_opt_level_pieces(OptionLongName,
                RevSortedSetAtOptLevels, 7, [], SetAtLevelPieces),
            EffDescPieces = EffDescPieces0 ++ SetAtLevelPieces
        else
            EffDescPieces = EffDescPieces0
        ),
        % ZZZ 71
        reflow_lines(help_texinfo, 71, EffDescPieces,
            CindexTopics, FindexTopics, ReflowLines),
        list.foldl(add_cindex_line_texinfo, CindexTopics, !IndexLineCord),
        list.foldl(add_findex_line_texinfo, FindexTopics, !IndexLineCord),
        BlankLineCord = cord.singleton(""),
        SpaceLine = cord.singleton("@sp 1"),
        !:LineCord = SpaceLine ++ !.OptLineCord ++ !.IndexLineCord ++
            cord.from_list(ReflowLines),
        (
            PublicOrPrivate = help_public,
            !:NumDocOpts = !.NumDocOpts + 1
        ;
            PublicOrPrivate = help_private,
            comment_out_texinfo_lines(!LineCord)
        ),
        !:EffectiveLinesCord = !.EffectiveLinesCord ++
            BlankLineCord ++ !.LineCord
    ).

:- pred acc_set_at_opt_level_pieces(string::in, list(set_at_opt_level)::in,
    int::in,
    list(help_piece)::in, list(help_piece)::out) is det.

acc_set_at_opt_level_pieces(_, [], _, !HelpPieces).
acc_set_at_opt_level_pieces(LongName, [SetAtOptLevel | LowerSetAtOptLevels],
        NextLevel, !HelpPieces) :-
    SetAtOptLevel = set_at_opt_level(Level, OptionData),
    ( if Level = NextLevel - 1  then
        string.format("Optimization level %d ",
            [i(Level)], LevelText),
        SetOrSets = "sets"
    else
        string.format("Optimization levels %d to %d ",
            [i(Level), i(NextLevel - 1)], LevelText),
        SetOrSets = "set"
    ),
    (
        OptionData = bool(Bool),
        ( Bool = no,  MaybeNoPrefix = "no-"
        ; Bool = yes, MaybeNoPrefix = ""
        ),
        string.format("automatically %s --%s%s.",
            [s(SetOrSets), s(MaybeNoPrefix), s(LongName)], SetText)
    ;
        OptionData = int(N),
        string.format("automatically %s --%s=%d.",
            [s(SetOrSets), s(LongName), i(N)], SetText)
    ),
    !:HelpPieces = [blank_line, w(LevelText ++ SetText) | !.HelpPieces],
    acc_set_at_opt_level_pieces(LongName, LowerSetAtOptLevels,
        Level, !HelpPieces).

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

:- pred acc_arg_align_text_plain(option_params::in, option::in, string::in,
    arg_align::in, cord(string)::in, cord(string)::out) is det.

acc_arg_align_text_plain(Params, Option, LongName, ArgAlign, !LineCord) :-
    Params = option_params(MaybeExpectArg, MaybeNegate,
        MaybeAddNegVersionOpt, IndexVersions),
    expect(unify(MaybeExpectArg, expect_arg), $pred,
        "unexpected MaybeExpectArg"),
    expect(unify(MaybeNegate, do_not_negate), $pred,
        "unexpected MaybeNegate"),
    expect(unify(MaybeAddNegVersionOpt, no_negative_version), $pred,
        "unexpected MaybeAddNegVersionOpt"),
    expect(unify(IndexVersions, index_positive_only), $pred,
        "unexpected IndexVersions"),

    ArgAlign = arg_align(ArgName, AlignedText, _),
    Line0 = long_option_name_line_plain(Params, Option,
        arg_name(ArgName), LongName),
    add_aligned_text(AlignedText, Line0, Line),
    cord.snoc(Line, !LineCord).

:- pred acc_arg_align_text_texinfo(option_params::in, option::in, string::in,
    arg_align::in, cord(string)::in, cord(string)::out,
    cord(string)::in, cord(string)::out) is det.

acc_arg_align_text_texinfo(Params, Option, LongName, ArgAlign,
        !OptLineCord, !IndexLineCord) :-
    Params = option_params(MaybeExpectArg, MaybeNegate,
        MaybeAddNegVersionOpt, IndexVersions),
    expect(unify(MaybeExpectArg, expect_arg), $pred,
        "unexpected MaybeExpectArg"),
    expect(unify(MaybeNegate, do_not_negate), $pred,
        "unexpected MaybeNegate"),
    expect(unify(MaybeAddNegVersionOpt, no_negative_version), $pred,
        "unexpected MaybeAddNegVersionOpt"),
    expect(unify(IndexVersions, index_positive_only), $pred,
        "unexpected IndexVersions"),

    ArgAlign = arg_align(ArgName, _, AlignedText),
    long_option_name_lines_texinfo(Params, Option, arg_name(ArgName), LongName,
        OptLine0, IndexLine),
    OptLine = OptLine0 ++ " " ++ AlignedText,
    add_option_line_texinfo(OptLine, !OptLineCord),
    add_findex_line_texinfo(IndexLine, !IndexLineCord).

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

:- type maybe_arg_name
    --->    no_arg
    ;       arg_name(string).

:- type maybe_aligned_text
    --->    no_align
    ;       aligned_text(string).

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

% The next four predicates next are needed because folds over
% acc_{long,short}_option_name_{plain,texinfo} do not preserve the inst of the
% maybe_aligned_text argument.

:- pred acc_long_option_names_plain(option_params, option, maybe_arg_name,
    maybe_aligned_text, list(string), cord(string), cord(string)).
:- mode acc_long_option_names_plain(in, in, in,
    in(bound(no_align)), in, in, out) is det.
:- mode acc_long_option_names_plain(in, in, in,
    in(bound(aligned_text(ground))), in, in, out) is det.

acc_long_option_names_plain(_, _, _, _, [], !LineCord).
acc_long_option_names_plain(Params, Option, MaybeArgName,
        MaybeAlignedText, [LongName | LongNames], !LineCord) :-
    acc_long_option_name_plain(Params, Option, MaybeArgName,
        MaybeAlignedText, LongName, !LineCord),
    acc_long_option_names_plain(Params, Option, MaybeArgName,
        MaybeAlignedText, LongNames, !LineCord).

:- pred acc_short_option_names_plain(option_params, option, maybe_arg_name,
    maybe_aligned_text, list(char), cord(string), cord(string)).
:- mode acc_short_option_names_plain(in, in, in,
    in(bound(no_align)), in, in, out) is det.
:- mode acc_short_option_names_plain(in, in, in,
    in(bound(aligned_text(ground))), in, in, out) is det.

acc_short_option_names_plain(_, _, _, _, [], !LineCord).
acc_short_option_names_plain(Params, Option, MaybeArgName,
        MaybeAlignedText, [ShortName | ShortNames], !LineCord) :-
    acc_short_option_name_plain(Params, Option, MaybeArgName,
        MaybeAlignedText, ShortName, !LineCord),
    acc_short_option_names_plain(Params, Option, MaybeArgName,
        MaybeAlignedText, ShortNames, !LineCord).

:- pred acc_long_option_names_texinfo(option_params, option, maybe_arg_name,
    maybe_aligned_text, list(string),
    cord(string), cord(string), cord(string), cord(string)).
:- mode acc_long_option_names_texinfo(in, in, in,
    in(bound(no_align)), in, in, out, in, out) is det.
:- mode acc_long_option_names_texinfo(in, in, in,
    in(bound(aligned_text(ground))), in, in, out, in, out) is det.

acc_long_option_names_texinfo(_, _, _, _, [], !OptLineCord, !IndexLineCord).
acc_long_option_names_texinfo(Params, Option, MaybeArgName,
        MaybeAlignedText, [LongName | LongNames],
        !OptLineCord, !IndexLineCord) :-
    acc_long_option_name_texinfo(Params, Option, MaybeArgName,
        MaybeAlignedText, LongName, !OptLineCord, !IndexLineCord),
    acc_long_option_names_texinfo(Params, Option, MaybeArgName,
        MaybeAlignedText, LongNames, !OptLineCord, !IndexLineCord).

:- pred acc_short_option_names_texinfo(option_params, option, maybe_arg_name,
    maybe_aligned_text, list(char),
    cord(string), cord(string), cord(string), cord(string)).
:- mode acc_short_option_names_texinfo(in, in, in,
    in(bound(no_align)), in, in, out, in, out) is det.
:- mode acc_short_option_names_texinfo(in, in, in,
    in(bound(aligned_text(ground))), in, in, out, in, out) is det.

acc_short_option_names_texinfo(_, _, _, _, [], !OptLineCord, !IndexLineCord).
acc_short_option_names_texinfo(Params, Option, MaybeArgName,
        MaybeAlignedText, [ShortName | ShortNames],
        !OptLineCord, !IndexLineCord) :-
    acc_short_option_name_texinfo(Params, Option, MaybeArgName,
        MaybeAlignedText, ShortName, !OptLineCord, !IndexLineCord),
    acc_short_option_names_texinfo(Params, Option, MaybeArgName,
        MaybeAlignedText, ShortNames, !OptLineCord, !IndexLineCord).

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

:- pred acc_long_option_name_plain(option_params, option, maybe_arg_name,
    maybe_aligned_text, string, cord(string), cord(string)).
:- mode acc_long_option_name_plain(in, in, in,
    in(bound(no_align)), in, in, out) is det.
:- mode acc_long_option_name_plain(in, in, in,
    in(bound(aligned_text(ground))), in, in, out) is det.

acc_long_option_name_plain(Params, Option, MaybeArgName, MaybeAlignedText,
        LongName, !LineCord) :-
    FirstLine0 = long_option_name_line_plain(Params, Option,
        MaybeArgName, LongName),
    (
        MaybeAlignedText = no_align,
        FirstLine = FirstLine0
    ;
        MaybeAlignedText = aligned_text(AlignedText),
        add_aligned_text(AlignedText, FirstLine0, FirstLine)
    ),
    cord.snoc(FirstLine, !LineCord),
    Params = option_params(_MaybeExpectArg, _MaybeNegate,
        MaybeAddNegVersionOpt, _IndexVersions),
    (
        MaybeAddNegVersionOpt = no_negative_version
    ;
        MaybeAddNegVersionOpt = add_negative_version,
        SecondLine = long_negated_option_name_line_plain(LongName),
        cord.snoc(SecondLine, !LineCord)
    ).

:- pred acc_short_option_name_plain(option_params, option, maybe_arg_name,
    maybe_aligned_text, char, cord(string), cord(string)).
:- mode acc_short_option_name_plain(in, in, in,
    in(bound(no_align)), in, in, out) is det.
:- mode acc_short_option_name_plain(in, in, in,
    in(bound(aligned_text(ground))), in, in, out) is det.

acc_short_option_name_plain(Params, Option, MaybeArgName, MaybeAlignedText,
        ShortName, !LineCord) :-
    FirstLine0 = short_option_name_line_plain(Params, Option, MaybeArgName,
        ShortName),
    (
        MaybeAlignedText = no_align,
        FirstLine = FirstLine0
    ;
        MaybeAlignedText = aligned_text(AlignedText),
        add_aligned_text(AlignedText, FirstLine0, FirstLine)
    ),
    cord.snoc(FirstLine, !LineCord),
    Params = option_params(_MaybeExpectArg, _MaybeNegate,
        MaybeAddNegVersionOpt, _IndexVersions),
    (
        MaybeAddNegVersionOpt = no_negative_version
    ;
        MaybeAddNegVersionOpt = add_negative_version,
        SecondLine = short_negated_option_name_line_plain(ShortName),
        cord.snoc(SecondLine, !LineCord)
    ).

:- pred acc_long_option_name_texinfo(option_params, option, maybe_arg_name,
    maybe_aligned_text, string,
    cord(string), cord(string), cord(string), cord(string)).
:- mode acc_long_option_name_texinfo(in, in, in,
    in(bound(no_align)), in, in, out, in, out) is det.
:- mode acc_long_option_name_texinfo(in, in, in,
    in(bound(aligned_text(ground))), in, in, out, in, out) is det.

acc_long_option_name_texinfo(Params, Option, MaybeArgName, MaybeAlignedText,
        LongName, !OptLineCord, !IndexLineCord) :-
    long_option_name_lines_texinfo(Params, Option, MaybeArgName, LongName,
        FirstOptLine0, _FirstIndexLine),
    (
        MaybeAlignedText = no_align,
        FirstOptLine = FirstOptLine0
    ;
        MaybeAlignedText = aligned_text(AlignedText),
        FirstOptLine = FirstOptLine0 ++ " " ++ AlignedText
    ),
    add_option_line_texinfo(FirstOptLine, !OptLineCord),
    Params = option_params(_MaybeExpectArg, _MaybeNegate,
        MaybeAddNegVersionOpt, IndexVersions),
    NegatedOptionName = long_negated_option_name_texinfo(LongName),
    (
        MaybeAddNegVersionOpt = no_negative_version
    ;
        MaybeAddNegVersionOpt = add_negative_version,
        % ZZZ
        add_option_line_texinfo(NegatedOptionName, !OptLineCord)
    ),
    PosParams = Params ^ op_negate := do_not_negate,
    NegParams = Params ^ op_negate := negate,
    % XXX We *could avoid this call if Params ^ op_negate is do_not_negate,
    % by simply setting PosIndexLine = _FirstIndexLine.
    long_option_name_lines_texinfo(PosParams, Option, MaybeArgName,
        LongName, _, PosIndexLine),
    % We cannot move the calls that create of the NegIndexLine here,
    % because that would lead to an assertion failure for non-negatable
    % options.
    (
        IndexVersions = index_positive_only,
        add_findex_line_texinfo(PosIndexLine, !IndexLineCord)
    ;
        IndexVersions = index_negative_only,
        long_option_name_lines_texinfo(NegParams, Option, MaybeArgName,
            LongName, _, NegIndexLine),
        add_findex_line_texinfo(NegIndexLine, !IndexLineCord)
    ;
        IndexVersions = index_positive_and_negative,
        long_option_name_lines_texinfo(NegParams, Option, MaybeArgName,
            LongName, _, NegIndexLine),
        add_findex_line_texinfo(PosIndexLine, !IndexLineCord),
        add_findex_line_texinfo(NegIndexLine, !IndexLineCord)
    ).

:- pred acc_short_option_name_texinfo(option_params, option, maybe_arg_name,
    maybe_aligned_text, char,
    cord(string), cord(string), cord(string), cord(string)).
:- mode acc_short_option_name_texinfo(in, in, in,
    in(bound(no_align)), in, in, out, in, out) is det.
:- mode acc_short_option_name_texinfo(in, in, in,
    in(bound(aligned_text(ground))), in, in, out, in, out) is det.

acc_short_option_name_texinfo(Params, Option, MaybeArgName, MaybeAlignedText,
        ShortName, !OptLineCord, !IndexLineCord) :-
    short_option_name_lines_texinfo(Params, Option, MaybeArgName, ShortName,
        FirstOptLine0, _FirstIndexLine),
    (
        MaybeAlignedText = no_align,
        FirstOptLine = FirstOptLine0
    ;
        MaybeAlignedText = aligned_text(AlignedText),
        FirstOptLine = FirstOptLine0 ++ " " ++ AlignedText
    ),
    add_option_line_texinfo(FirstOptLine, !OptLineCord),
    Params = option_params(_MaybeExpectArg, _MaybeNegate,
        MaybeAddNegVersionOpt, IndexVersions),
    NegatedOptionName = short_negated_option_name_texinfo(ShortName),
    (
        MaybeAddNegVersionOpt = no_negative_version
    ;
        MaybeAddNegVersionOpt = add_negative_version,
        % ZZZ
        add_option_line_texinfo(NegatedOptionName, !OptLineCord)
    ),
    PosParams = Params ^ op_negate := do_not_negate,
    NegParams = Params ^ op_negate := negate,
    % XXX We *could avoid this call if Params ^ op_negate is do_not_negate,
    % by simply setting PosIndexLine = _FirstIndexLine.
    short_option_name_lines_texinfo(PosParams, Option, MaybeArgName,
        ShortName, _, PosIndexLine),
    % We cannot move the calls that create of the NegIndexLine here,
    % because that would lead to an assertion failure for non-negatable
    % options.
    (
        IndexVersions = index_positive_only,
        add_findex_line_texinfo(PosIndexLine, !IndexLineCord)
    ;
        IndexVersions = index_negative_only,
        short_option_name_lines_texinfo(NegParams, Option, MaybeArgName,
            ShortName, _, NegIndexLine),
        add_findex_line_texinfo(NegIndexLine, !IndexLineCord)
    ;
        IndexVersions = index_positive_and_negative,
        short_option_name_lines_texinfo(NegParams, Option, MaybeArgName,
            ShortName, _, NegIndexLine),
        add_findex_line_texinfo(PosIndexLine, !IndexLineCord),
        add_findex_line_texinfo(NegIndexLine, !IndexLineCord)
    ).

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

:- func long_option_name_line_plain(option_params, option, maybe_arg_name,
    string) = string.

long_option_name_line_plain(Params, Option, MaybeArgName, LongName0) = Line :-
    Indent = single_indent,
    Params = option_params(MaybeExpectArg, MaybeNegate,
        _MaybeAddNegVersionOpt, _IndexVersions),
    (
        MaybeNegate = negate,
        expect(unify(MaybeArgName, no_arg), $pred, "MaybeArgName != no_arg"),
        maybe_have_arg(MaybeExpectArg, Option, MaybeArgName,
            LongName0, LongName),
        Line = long_negated_option_name_line_plain(LongName)
    ;
        MaybeNegate = do_not_negate,
        (
            MaybeArgName = no_arg,
            have_no_arg(MaybeExpectArg, Option, LongName0, LongName),
            string.format("%s--%s", [s(Indent), s(LongName)], Line)
        ;
            MaybeArgName = arg_name(ArgName),
            have_arg(MaybeExpectArg, Option, LongName0, LongName),
            MaybeWrappedArgName = maybe_wrap_arg_name_plain(Option, ArgName),
            string.format("%s--%s %s",
                [s(Indent), s(LongName), s(MaybeWrappedArgName)], Line)
        )
    ).

:- func short_option_name_line_plain(option_params, option, maybe_arg_name,
    char) = string.

short_option_name_line_plain(Params, Option, MaybeArgName, ShortName0)
        = Line :-
    Indent = single_indent,
    Params = option_params(MaybeExpectArg, MaybeNegate,
        _MaybeAddNegVersionOpt, _IndexVersions),
    (
        MaybeNegate = negate,
        expect(unify(MaybeArgName, no_arg), $pred, "MaybeArgName != no_arg"),
        maybe_have_arg(MaybeExpectArg, Option, MaybeArgName,
            ShortName0, ShortName),
        Line = short_negated_option_name_line_plain(ShortName)
    ;
        MaybeNegate = do_not_negate,
        (
            MaybeArgName = no_arg,
            have_no_arg(MaybeExpectArg, Option, ShortName0, ShortName),
            string.format("%s-%c", [s(Indent), c(ShortName)], Line)
        ;
            MaybeArgName = arg_name(ArgName),
            have_arg(MaybeExpectArg, Option, ShortName0, ShortName),
            MaybeWrappedArgName = maybe_wrap_arg_name_plain(Option, ArgName),
            string.format("%s-%c %s",
                [s(Indent), c(ShortName), s(MaybeWrappedArgName)], Line)
        )
    ).

:- pred long_option_name_lines_texinfo(option_params::in, option::in,
    maybe_arg_name::in, string::in, string::out, string::out) is det.

long_option_name_lines_texinfo(Params, Option, MaybeArgName, LongName0,
        OptLine, IndexLine) :-
    Params = option_params(MaybeExpectArg, MaybeNegate,
        _MaybeAddNegVersionOpt, _IndexVersions),
    (
        MaybeNegate = negate,
        maybe_have_arg(MaybeExpectArg, Option, MaybeArgName,
            LongName0, LongName),
        IndexLine = long_negated_option_name_texinfo(LongName),
        string.format("@code{--no-%s}", [s(LongName)], OptLine)
    ;
        MaybeNegate = do_not_negate,
        (
            MaybeArgName = no_arg,
            have_no_arg(MaybeExpectArg, Option, LongName0, LongName),
            string.format("--%s", [s(LongName)], IndexLine),
            string.format("@code{--%s}", [s(LongName)], OptLine)
        ;
            MaybeArgName = arg_name(ArgName),
            have_arg(MaybeExpectArg, Option, LongName0, LongName),
            MaybeWrappedArgName = maybe_wrap_arg_name_texinfo(Option, ArgName),
            string.format("--%s", [s(LongName)], IndexLine),
            string.format("@code{--%s %s}",
                [s(LongName), s(MaybeWrappedArgName)], OptLine)
        )
    ).

:- pred short_option_name_lines_texinfo(option_params::in, option::in,
    maybe_arg_name::in, char::in, string::out, string::out) is det.

short_option_name_lines_texinfo(Params, Option, MaybeArgName, ShortName0,
        OptLine, IndexLine) :-
    Params = option_params(MaybeExpectArg, MaybeNegate,
        _MaybeAddNegVersionOpt, _IndexVersions),
    (
        MaybeNegate = negate,
        maybe_have_arg(MaybeExpectArg, Option, MaybeArgName,
            ShortName0, ShortName),
        IndexLine = short_negated_option_name_texinfo(ShortName),
        string.format("@code{-%c}", [c(ShortName)], OptLine)
    ;
        MaybeNegate = do_not_negate,
        (
            MaybeArgName = no_arg,
            have_no_arg(MaybeExpectArg, Option, ShortName0, ShortName),
            string.format("-%c", [c(ShortName)], IndexLine),
            string.format("@code{-%c}", [c(ShortName)], OptLine)
        ;
            MaybeArgName = arg_name(ArgName),
            have_arg(MaybeExpectArg, Option, ShortName0, ShortName),
            MaybeWrappedArgName = maybe_wrap_arg_name_texinfo(Option, ArgName),
            string.format("-%c", [c(ShortName)], IndexLine),
            string.format("@code{-%c %s}",
                [c(ShortName), s(MaybeWrappedArgName)], OptLine)
        )
    ).

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

:- func long_negated_option_name_line_plain(string) = string.

long_negated_option_name_line_plain(LongName) = Line :-
    Indent = single_indent,
    string.format("%s--no-%s", [s(Indent), s(LongName)], Line).

:- func short_negated_option_name_line_plain(char) = string.

short_negated_option_name_line_plain(ShortName) = Line :-
    Indent = single_indent,
    string.format("%s-%c-", [s(Indent), c(ShortName)], Line).

:- func long_negated_option_name_texinfo(string) = string.

long_negated_option_name_texinfo(LongName) = NegatedLongName :-
    string.format("--no-%s", [s(LongName)], NegatedLongName).

:- func short_negated_option_name_texinfo(char) = string.

short_negated_option_name_texinfo(ShortName) = NegatedShortName :-
    string.format("-%c-", [c(ShortName)], NegatedShortName).

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

:- func maybe_wrap_arg_name_plain(option, string) = string.

maybe_wrap_arg_name_plain(Option, ArgName) = MaybeWrappedArgName :-
    % Do not put <>s around argument "names" that are actually not names,
    % but instead are either
    %
    % - sets of the allowed values wrapped in {}s, or
    % - default optimization levels, such as -O2.
    ( if
        ArgName = ""
    then
        unexpected($pred, string(Option) ++ " has empty arg name")
    else if
        ( string.find_first_char(ArgName, '{', _)
        ; string.find_first_char(ArgName, '-', _)
        )
    then
        MaybeWrappedArgName = ArgName
    else
        MaybeWrappedArgName = "<" ++ ArgName ++ ">"
    ).

:- func maybe_wrap_arg_name_texinfo(option, string) = string.

maybe_wrap_arg_name_texinfo(Option, ArgName) = MaybeWrappedArgName :-
    % Do not put @var{} around argument "names" that are actually not names,
    % but instead are either
    %
    % - sets of the allowed values wrapped in {}s, or
    % - default optimization levels, such as -O2.
    ( if ArgName = "" then
        unexpected($pred, string(Option) ++ " has empty arg name")
    else if string.find_first_char(ArgName, '{', _) then
        string.replace_all(ArgName, "{", "@{", ArgName1),
        string.replace_all(ArgName1, "}", "@}", MaybeWrappedArgName)
    else if string.index(ArgName, 0, '-') then
        MaybeWrappedArgName = ArgName
    else
        string.format("@var{%s}", [s(ArgName)], MaybeWrappedArgName)
    ).

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

:- pred add_option_line_texinfo(string::in,
    cord(string)::in, cord(string)::out) is det.

add_option_line_texinfo(OptionStr, !OptLineCord) :-
    ( if cord.is_empty(!.OptLineCord) then
        ItemStr = "@item"
    else
        ItemStr = "@itemx"
    ),
    string.format("%s %s", [s(ItemStr), s(OptionStr)], OptLine),
    cord.snoc(OptLine, !OptLineCord).

:- pred add_findex_line_texinfo(string::in,
    cord(string)::in, cord(string)::out) is det.

add_findex_line_texinfo(OptionStr, !IndexLineCord) :-
    string.format("@findex %s", [s(OptionStr)], IndexLine),
    cord.snoc(IndexLine, !IndexLineCord).

:- pred add_cindex_line_texinfo(string::in,
    cord(string)::in, cord(string)::out) is det.

add_cindex_line_texinfo(Topic, !IndexLineCord) :-
    string.format("@cindex %s", [s(Topic)], IndexLine),
    cord.snoc(IndexLine, !IndexLineCord).

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

% The next three predicates all return the last input argument unchanged
% as their only output argument. The jobs of this output argument is simply
% to prevent calls to these predicates from being optimized away.

:- pred maybe_have_arg(maybe_expect_arg::in, option::in, maybe_arg_name::in,
    T::in, T::out) is det.

maybe_have_arg(MaybeExpectArg, Option, MaybeArgName,
        OptionName0, OptionName) :-
    (
        MaybeArgName = no_arg,
        have_no_arg(MaybeExpectArg, Option, OptionName0, OptionName)
    ;
        MaybeArgName = arg_name(_),
        have_arg(MaybeExpectArg, Option, OptionName0, OptionName)
    ).

:- pred have_no_arg(maybe_expect_arg::in, option::in, T::in, T::out) is det.

have_no_arg(MaybeExpectArg, Option, OptionName0, OptionName) :-
    (
        MaybeExpectArg = do_not_expect_arg,
        OptionName = OptionName0
    ;
        MaybeExpectArg = expect_arg,
        string.format("missing arg for %s", [s(string(Option))], Msg),
        unexpected($pred, Msg)
    ).

:- pred have_arg(maybe_expect_arg::in, option::in, T::in, T::out) is det.

have_arg(MaybeExpectArg, Option, OptionName0, OptionName) :-
    (
        MaybeExpectArg = do_not_expect_arg,
        string.format("unexpected arg for %s", [s(string(Option))], Msg),
        unexpected($pred, Msg)
    ;
        MaybeExpectArg = expect_arg,
        OptionName = OptionName0
    ).

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

:- pred reflow_lines(help_format, int, list(help_piece),
    list(string), list(string), list(string)).
:- mode reflow_lines(in(help_plain_text), in, in, out, out, out) is det.
:- mode reflow_lines(in(help_texinfo), in, in, out, out, out) is det.

reflow_lines(Format, LineLen, Pieces, CindexTopics, FindexTopics,
        FinishedLines) :-
    % string.count_code_points(IndentStr, IndentLen),
    % AvailLen = LineLen - IndentLen,
    reflow_lines_loop_over_lines(Format, LineLen, Pieces,
        cord.init, CindexCord, cord.init, FindexCord,
        0, _CurLineLen, cord.init, CurLine1, cord.init, FinishedLineCord1),
    CindexTopics = cord.list(CindexCord),
    FindexTopics = cord.list(FindexCord),
    finish_cur_line(CurLine1, FinishedLineCord1, FinishedLineCord),
    FinishedLines = cord.list(FinishedLineCord).

    % The pieces of the current line.
    % This will NOT contain the initial indent string.
    % This WILL contain both the words put on this line so far, *and*
    % the spaces between them.
:- type cur_line == cord(string).

    % The reflowed lines we have already constructed.
:- type finished_lines == cord(string).

:- pred reflow_lines_loop_over_lines(help_format, int, list(help_piece),
    cord(string), cord(string), cord(string), cord(string),
    int, int, cur_line, cur_line, finished_lines, finished_lines).
:- mode reflow_lines_loop_over_lines(in(help_plain_text), in, in,
    in, out, in, out, in, out, in, out, in, out) is det.
:- mode reflow_lines_loop_over_lines(in(help_texinfo), in, in,
    in, out, in, out, in, out, in, out, in, out) is det.

reflow_lines_loop_over_lines(Format, LineLen, Pieces, !CindexCord, !FindexCord,
        !CurLineLen, !CurLine, !FinishedLineCord) :-
    (
        Pieces = []
    ;
        Pieces = [HeadPiece | TailPieces],
        (
            HeadPiece = w(WordsStr),
            Words0 = string.words(WordsStr),
            (
                Format = help_plain_text,
                Words = Words0
            ;
                Format = help_texinfo,
                list.map(replace_all_sv("e.g.", "e.g.@:"), Words0, Words)
            ),
            reflow_lines_loop_over_words(LineLen, Words, !CurLine, !CurLineLen,
                !FinishedLineCord)
        ;
            HeadPiece = fixed(FixedStr),
            add_word(LineLen, FixedStr, !CurLine, !CurLineLen,
                !FinishedLineCord)
        ;
            (
                ( HeadPiece = quote(Text), Suffix = ""
                ; HeadPiece = quote(Text, Suffix)
                ),
                (
                    Format = help_plain_text,
                    string.format("`%s'%s", [s(Text), s(Suffix)], Str)
                ;
                    Format = help_texinfo,
                    string.format("``%s''%s", [s(Text), s(Suffix)], Str)
                )
            ;
                ( HeadPiece = opt(Option), Suffix = ""
                ; HeadPiece = opt(Option, Suffix)
                ),
                (
                    Format = help_plain_text,
                    string.format("`%s'%s", [s(Option), s(Suffix)], Str)
                ;
                    Format = help_texinfo,
                    string.format("@samp{%s}%s", [s(Option), s(Suffix)], Str)
                )
            ;
                ( HeadPiece = arg(Arg), Suffix = ""
                ; HeadPiece = arg(Arg, Suffix)
                ),
                (
                    Format = help_plain_text,
                    string.format("<%s>%s", [s(Arg), s(Suffix)], Str)
                ;
                    Format = help_texinfo,
                    string.format("@var{%s}%s", [s(Arg), s(Suffix)], Str)
                )
            ;
                ( HeadPiece = bare_arg(Arg), Suffix = ""
                ; HeadPiece = bare_arg(Arg, Suffix)
                ),
                (
                    Format = help_plain_text,
                    string.format("%s%s", [s(Arg), s(Suffix)], Str)
                ;
                    Format = help_texinfo,
                    string.format("@samp{%s}%s", [s(Arg), s(Suffix)], Str)
                )
            ;
                ( HeadPiece = opt_arg(Option, Arg), Suffix = ""
                ; HeadPiece = opt_arg(Option, Arg, Suffix)
                ),
                (
                    Format = help_plain_text,
                    string.format("`%s <%s>'%s",
                        [s(Option), s(Arg), s(Suffix)], Str)
                ;
                    Format = help_texinfo,
                    string.format("@samp{%s @var{%s}}%s",
                        [s(Option), s(Arg), s(Suffix)], Str)
                )
            ;
                ( HeadPiece = samp(Option), Suffix = ""
                ; HeadPiece = samp(Option, Suffix)
                ),
                (
                    Format = help_plain_text,
                    string.format("`%s'%s", [s(Option), s(Suffix)], Str)
                ;
                    Format = help_texinfo,
                    string.format("@samp{%s}%s", [s(Option), s(Suffix)], Str)
                )
            ;
                ( HeadPiece = emph(Text), Suffix = ""
                ; HeadPiece = emph(Text, Suffix)
                ),
                (
                    Format = help_plain_text,
                    string.format("*%s*%s", [s(Text), s(Suffix)], Str)
                ;
                    Format = help_texinfo,
                    string.format("@emph{%s}%s", [s(Text), s(Suffix)], Str)
                )
            ;
                ( HeadPiece = env(Code), Suffix = ""
                ; HeadPiece = env(Code, Suffix)
                ),
                (
                    Format = help_plain_text,
                    string.format("`%s'%s", [s(Code), s(Suffix)], Str)
                ;
                    Format = help_texinfo,
                    string.format("@env{%s}%s", [s(Code), s(Suffix)], Str)
                )
            ;
                ( HeadPiece = code(Code), Suffix = ""
                ; HeadPiece = code(Code, Suffix)
                ),
                (
                    Format = help_plain_text,
                    string.format("`%s'%s", [s(Code), s(Suffix)], Str)
                ;
                    Format = help_texinfo,
                    string.format("@code{%s}%s", [s(Code), s(Suffix)], Str)
                )
            ;
                ( HeadPiece = file(Var), Suffix = ""
                ; HeadPiece = file(Var, Suffix)
                ),
                (
                    Format = help_plain_text,
                    string.format("`%s'%s", [s(Var), s(Suffix)], Str)
                ;
                    Format = help_texinfo,
                    string.format("@file{%s}%s", [s(Var), s(Suffix)], Str)
                )
            ;
                ( HeadPiece = var(Var), Suffix = ""
                ; HeadPiece = var(Var, Suffix)
                ),
                (
                    Format = help_plain_text,
                    string.format("%s%s", [s(Var), s(Suffix)], Str)
                ;
                    Format = help_texinfo,
                    string.format("@var{%s}%s", [s(Var), s(Suffix)], Str)
                )
            ;
                ( HeadPiece = file_var(File, Ext), Suffix = ""
                ; HeadPiece = file_var(File, Ext, Suffix)
                ),
                (
                    Format = help_plain_text,
                    string.format("`<%s>.%s'%s",
                        [s(File), s(Ext), s(Suffix)], Str)
                ;
                    Format = help_texinfo,
                    string.format("@file{@var{%s}.%s}%s",
                        [s(File), s(Ext), s(Suffix)], Str)
                )
            ),
            add_word(LineLen, Str, !CurLine, !CurLineLen, !FinishedLineCord)
        ;
            (
                ( HeadPiece = ref(Before0, RefName, After0), Suffix = ""
                ; HeadPiece = ref(Before0, RefName, After0, Suffix)
                ),
                (
                    Format = help_plain_text,
                    string.format("%s\"%s\"%s%s",
                        [s(before_str(Before0)), s(RefName),
                        s(after_str(After0)), s(Suffix)], Str)
                ;
                    Format = help_texinfo,
                    % We ignore Before0 and After0,
                    % which are for help text only;
                    % for texinfo, the @ref should supply them.
                    string.format("@ref{%s}%s", [s(RefName), s(Suffix)], Str)
                )
            ;
                ( HeadPiece = xref(RefName), Suffix = ""
                ; HeadPiece = xref(RefName, Suffix)
                ),
                (
                    Format = help_plain_text,
                    % xref is a texinfo only piece. It should occur
                    % only inside texinfo_only wrappers.
                    Str = ""
                ;
                    Format = help_texinfo,
                    string.format("@xref{%s}%s", [s(RefName), s(Suffix)], Str)
                )
            ),
            Words = string.words(Str),
            reflow_lines_loop_over_words(LineLen, Words, !CurLine, !CurLineLen,
                !FinishedLineCord)
        ;
            HeadPiece = blank_line,
            finish_cur_line(!.CurLine, !FinishedLineCord),
            (
                Format = help_plain_text,
                cord.snoc("", !FinishedLineCord)
            ;
                Format = help_texinfo,
                cord.snoc("", !FinishedLineCord),
                cord.snoc("@sp 1", !FinishedLineCord)
            ),
            !:CurLine = cord.init,
            !:CurLineLen = 0
        ;
            HeadPiece = help_text_only(HelpTextPieces),
            (
                Format = help_plain_text,
                reflow_lines_loop_over_lines(Format, LineLen, HelpTextPieces,
                    !CindexCord, !FindexCord,
                    !CurLineLen, !CurLine, !FinishedLineCord)
            ;
                Format = help_texinfo
            )
        ;
            HeadPiece = texinfo_only(TexInfoPieces),
            (
                Format = help_plain_text
            ;
                Format = help_texinfo,
                reflow_lines_loop_over_lines(Format, LineLen, TexInfoPieces,
                    !CindexCord, !FindexCord,
                    !CurLineLen, !CurLine, !FinishedLineCord)
            )
        ;
            HeadPiece = help_text_texinfo(HelpTextPieces, TexInfoPieces),
            (
                Format = help_plain_text,
                reflow_lines_loop_over_lines(Format, LineLen, HelpTextPieces,
                    !CindexCord, !FindexCord,
                    !CurLineLen, !CurLine, !FinishedLineCord)
            ;
                Format = help_texinfo,
                reflow_lines_loop_over_lines(Format, LineLen, TexInfoPieces,
                    !CindexCord, !FindexCord,
                    !CurLineLen, !CurLine, !FinishedLineCord)
            )
        ;
            HeadPiece = cindex(Topic),
            (
                Format = help_plain_text
            ;
                Format = help_texinfo,
                cord.snoc(Topic, !CindexCord)
            )
        ;
            HeadPiece = findex(Topic),
            (
                Format = help_plain_text
            ;
                Format = help_texinfo,
                cord.snoc(Topic, !FindexCord)
            )
        ),
        reflow_lines_loop_over_lines(Format, LineLen, TailPieces,
            !CindexCord, !FindexCord, !CurLineLen, !CurLine, !FinishedLineCord)
    ).

:- func before_str(string) = string.

before_str(BeforeStr0) = BeforeStr :-
    ( if BeforeStr0 = "" then
        BeforeStr = ""
    else
        BeforeStr = BeforeStr0 ++ " "
    ).

:- func after_str(string) = string.

after_str(AfterStr0) = AfterStr :-
    ( if AfterStr0 = "" then
        AfterStr = ""
    else
        AfterStr = " " ++ AfterStr0
    ).

:- pred reflow_lines_loop_over_words(int::in, list(string)::in,
    cur_line::in, cur_line::out, int::in, int::out,
    finished_lines::in, finished_lines::out) is det.

reflow_lines_loop_over_words(LineLen, Words, !CurLine, !CurLineLen,
        !FinishedLineCord) :-
    (
        Words = []
    ;
        Words = [HeadWord | TailWords],
        add_word(LineLen, HeadWord, !CurLine, !CurLineLen, !FinishedLineCord),
        reflow_lines_loop_over_words(LineLen, TailWords, !CurLine, !CurLineLen,
            !FinishedLineCord)
    ).

:- pred add_word(int::in, string::in,
    cur_line::in, cur_line::out, int::in, int::out,
    finished_lines::in, finished_lines::out) is det.

add_word(LineLen, Word, !CurLine, !CurLineLen, !FinishedLineCord) :-
    string.count_code_points(Word, WordLen),
    ( if WordLen = 0 then
        true
    else if !.CurLineLen = 0 then
        !:CurLine = cord.singleton(Word),
        !:CurLineLen = WordLen
    else
        NextLineLen = !.CurLineLen + 1 + WordLen,
        ( if NextLineLen =< LineLen then
            cord.snoc(" ", !CurLine),
            cord.snoc(Word, !CurLine),
            !:CurLineLen = NextLineLen
        else
            finish_cur_line(!.CurLine, !FinishedLineCord),
            !:CurLine = cord.singleton(Word),
            !:CurLineLen = WordLen
        )
    ).

:- pred finish_cur_line(cur_line::in,
    finished_lines::in, finished_lines::out) is det.

finish_cur_line(CurLine, !FinishedLineCord) :-
    FinishedLine = string.append_list(cord.list(CurLine)),
    ( if FinishedLine = "" then
        true
    else
        cord.snoc(FinishedLine, !FinishedLineCord)
    ).

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

list_optimization_options(Stream, MaybeUpTo, !IO) :-
    acc_optimization_options_loop(MaybeUpTo, 0, cord.init, LineCord),
    write_lines(Stream, cord.list(LineCord), !IO).

:- pred acc_optimization_options_loop(maybe(int)::in, int::in,
    cord(string)::in, cord(string)::out) is det.

acc_optimization_options_loop(MaybeUpTo, CurLevel, !LineCord) :-
    ( if
        (
            MaybeUpTo = no
        ;
            MaybeUpTo = yes(UpTo),
            CurLevel =< UpTo
        ),
        opts_enabled_at_level(CurLevel, LevelDescLines, DocumentedOpts)
    then
        string.format("Optimization level %d:", [i(CurLevel)],
            LevelHeading),
        string.format("%sThe options set at this level are:",
            [s(single_indent)], SetAtLevel),
        list.foldl(document_one_optimization_option, DocumentedOpts,
            [], OptLines),
        % Sorting makes options slightly easier to find in a list of options.
        % It also makes the output look more systematic.
        list.sort(OptLines, SortedOptLines),

        ( if CurLevel > 0 then
            cord.snoc("", !LineCord)
        else
            true
        ),
        cord.snoc(LevelHeading, !LineCord),
        (
            LevelDescLines = []
        ;
            LevelDescLines = [_ | _],
            cord.snoc("", !LineCord),
            list.foldl(acc_prefixed_line(single_indent), LevelDescLines,
                !LineCord)
        ),
        cord.snoc("", !LineCord),
        cord.snoc(SetAtLevel, !LineCord),
        cord.snoc("", !LineCord),
        list.foldl(acc_prefixed_line(double_indent), SortedOptLines,
            !LineCord),

        acc_optimization_options_loop(MaybeUpTo, CurLevel + 1, !LineCord)
    else
        true
    ).

:- pred document_one_optimization_option(documented_optimization_option::in,
    list(string)::in, list(string)::out) is det.

document_one_optimization_option(DocOpt, !Lines) :-
    DocOpt = doc_oo(_, Option, OptionData),
    get_main_long_name(Option, MaybeLongName),
    (
        MaybeLongName = no
    ;
        MaybeLongName = yes(LongName),
        (
            OptionData = bool(Bool),
            (
                Bool = no,
                string.format("--no-%s", [s(LongName)], Line)
            ;
                Bool = yes,
                string.format("--%s", [s(LongName)], Line)
            ),
            !:Lines = [Line | !.Lines]
        ;
            OptionData = int(Int),
            string.format("--%s=%d", [s(LongName), i(Int)], Line),
            !:Lines = [Line | !.Lines]
        )
    ).

:- pred get_main_long_name(option::in, maybe(string)::out) is det.

get_main_long_name(Option, MaybeLongName) :-
    optdb(_, Option, _, Help),
    (
        ( Help = no_help
        ; Help = unnamed_help(_)
        ),
        MaybeLongName = no
    ;
        ( Help = gen_help(_, LongName, _, _, _)
        ; Help = help(LongName, _)
        ; Help = arg_help(LongName, _, _)
        ; Help = priv_help(LongName, _)
        ; Help = priv_arg_help(LongName, _, _)
        ; Help = alt_help(LongName, _, _)
        ; Help = alt_arg_help(LongName, _, _, _)
        ; Help = priv_alt_help(LongName, _, _)
        ; Help = priv_alt_arg_help(LongName, _, _, _)
        ; Help = short_help(_, LongName, _, _)
        ; Help = short_arg_help(_, LongName, _, _, _)
        ; Help = priv_short_help(_, LongName, _, _)
        ; Help = priv_short_arg_help(_, LongName, _, _, _)
        ; Help = alt_align_help(LongName, _, _, _, _)
        ; Help = priv_alt_align_help(LongName, _, _, _, _)
        ; Help = short_alt_align_help(_, LongName, _, _, _, _)
        ; Help = no_align_help(LongName, _, _, _, _, _)
        ; Help = alt_arg_align_help(LongName, _, _)
        ),
        MaybeLongName = yes(LongName)
    ).

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

:- pred add_aligned_text(string::in, string::in, string::out) is det.

add_aligned_text(AlignedText, Line0, Line) :-
    string.format("%-39s %s", [s(Line0), s(AlignedText)], Line).

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

:- func single_indent = string.
:- func double_indent = string.

single_indent = "    ".
double_indent = "        ".

:- pred acc_prefixed_line(string::in, string::in,
    cord(string)::in, cord(string)::out) is det.

acc_prefixed_line(Prefix, LineBody, !LineCord) :-
    Line = Prefix ++ LineBody,
    cord.snoc(Line, !LineCord).

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

:- pred is_bool(option_data::in) is semidet.

is_bool(bool(_)).

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

:- func menu_items_to_menu(list(menu_item)) = list(string).

menu_items_to_menu(MenuItems) = MenuLines :-
    MenuItemLines = list.map(menu_item_to_menu_line, MenuItems),
    MenuLines = ["@menu"] ++ MenuItemLines ++ ["@end menu"].

:- func menu_item_to_menu_line(menu_item) = string.

menu_item_to_menu_line(MenuItem) = Line :-
    MenuItem = menu_item(Name, Desc),
    string.format("* %s:: %s", [s(Name), s(Desc)], Line).

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

:- pred add_node_line(string::in, string::in,
    cord(string)::in, cord(string)::out) is det.

add_node_line(NodeCmd, SectionName, !LineCord) :-
    string.format("%-15s %s", [s(NodeCmd), s(SectionName)], Line),
    cord.snoc(Line, !LineCord).

:- pred comment_out_texinfo_lines(cord(string)::in, cord(string)::out) is det.

comment_out_texinfo_lines(!LineCord) :-
    !:LineCord = cord.map(comment_out_texinfo_line, !.LineCord).

:- func comment_out_texinfo_line(string) = string.

comment_out_texinfo_line(Line) = CommentedOutLine :-
    % It is somewhat easier to read commented out sections of the user's guide
    % if lines containing only "@c " do NOT obscure (sub)section boundaries.
    ( if Line = "" then
        CommentedOutLine = ""
    else
        CommentedOutLine = "@c " ++ Line
    ).

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

:- pred write_lines(io.text_output_stream::in, list(string)::in,
    io::di, io::uo) is det.

write_lines(_, [], !IO).
write_lines(Stream, [Line | Lines], !IO) :-
    io.write_string(Stream, Line, !IO),
    io.nl(Stream, !IO),
    write_lines(Stream, Lines, !IO).

%---------------------------------------------------------------------------%
:- end_module libs.print_help.
%---------------------------------------------------------------------------%
