%---------------------------------------------------------------------------%
% vim: ft=mercury ts=4 sw=4 et
%---------------------------------------------------------------------------%
% Copyright (C) 1996-2011 The University of Melbourne.
% Copyright (C) 2016-2024 The Mercury team.
% This file may only be copied under the terms of the GNU General
% Public License - see the file COPYING in the Mercury distribution.
%---------------------------------------------------------------------------%
%
% File: parse_goal.m.
% Main authors: fjh, zs.
%
% This module defines the predicates that parse goals.
%
%---------------------------------------------------------------------------%

:- module parse_tree.parse_goal.
:- interface.

:- import_module parse_tree.error_spec.
:- import_module parse_tree.maybe_error.
:- import_module parse_tree.prog_data.
:- import_module parse_tree.prog_item.

:- import_module cord.
:- import_module list.
:- import_module term.

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

    % Convert a single term into a goal.
    %
:- pred parse_goal(term::in, cord(format_piece)::in,
    maybe2(goal, list(warning_spec))::out,
    prog_varset::in, prog_varset::out) is det.

    % Convert a term, possibly starting with `some [Vars]', into a list
    % of the quantified variables, a list of quantified state variables,
    % and a goal. (If the term doesn't start with `some [Vars]', we return
    % empty lists of variables.)
    %
    % Exported to superhomogeneous.m for parsing if-then-else expressions.
    %
:- pred parse_some_vars_goal(term::in, cord(format_piece)::in,
    maybe4(list(prog_var), list(prog_var), goal, list(warning_spec))::out,
    prog_varset::in, prog_varset::out) is det.

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

:- implementation.

:- import_module mdbcomp.
:- import_module mdbcomp.sym_name.
:- import_module parse_tree.parse_goal_util.
:- import_module parse_tree.parse_sym_name.
:- import_module parse_tree.parse_tree_out_misc.
:- import_module parse_tree.parse_tree_out_term.
:- import_module parse_tree.parse_vars.

:- import_module assoc_list.
:- import_module bag.
:- import_module char.
:- import_module int.
:- import_module maybe.
:- import_module pair.
:- import_module require.
:- import_module solutions.
:- import_module string.
:- import_module term_context.
:- import_module unit.
:- import_module varset.

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

parse_goal(Term, ContextPieces, MaybeGoal, !VarSet) :-
    % We could do some error-checking here, but all errors are picked up
    % in either the type-checker or parser anyway.
    %
    % We just check if it matches the appropriate pattern for one of the
    % builtins. If it doesn't match any of the builtins, then it's just
    % a predicate call.
    ( if
        % Check for builtins...
        Term = term.functor(term.atom(Name), ArgTerms, Context),
        string_goal_kind(Name, GoalKind)
    then
        parse_non_call_goal(GoalKind, ArgTerms, Context, ContextPieces,
            MaybeGoal, !VarSet)
    else
        % It is not a builtin.
        Context = get_term_context(Term),
        term.coerce(Term, ProgTerm),
        % Check for predicate calls.
        ( if try_parse_sym_name_and_args(ProgTerm, SymName, ArgTerms) then
            Goal = call_expr(Context, SymName, ArgTerms, purity_pure)
        else
            % A call to a free variable, or to a number or string.
            % Just translate it into a call to call/1 - the typechecker
            % will catch calls to numbers and strings.
            Goal = call_expr(Context, unqualified("call"), [ProgTerm],
                purity_pure)
        ),
        MaybeGoal = ok2(Goal, [])
    ).

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

parse_some_vars_goal(Term, ContextPieces, MaybeVarsAndGoal, !VarSet) :-
    % We parse existentially quantified goals in non-DCG contexts here,
    % while we parse them in DCG contexts in parse_some_vars_dcg_goal
    % in parse_dcg_goal.m.
    ( if
        Term = term.functor(term.atom("some"), [VarsTerm, SubGoalTerm],
            _Context)
    then
        varset.coerce(!.VarSet, GenericVarSet),
        VarsTailPieces = [lower_case_next_if_not_first,
            words("In first argument of"), quote("some"), suffix(":")],
        VarsContextPieces = ContextPieces ++ cord.from_list(VarsTailPieces),
        parse_vars_state_vars(VarsTerm, GenericVarSet, VarsContextPieces,
            MaybeVars),
        GoalTerm = SubGoalTerm
    else
        MaybeVars = ok1(plain_state_vars([], [])),
        GoalTerm = Term
    ),
    parse_goal(GoalTerm, ContextPieces, MaybeGoal, !VarSet),
    ( if
        MaybeVars = ok1(plain_state_vars(Vars0, StateVars0)),
        MaybeGoal = ok2(Goal, SubGoalWarningSpecs)
    then
        list.map(term.coerce_var, Vars0, Vars),
        list.map(term.coerce_var, StateVars0, StateVars),
        MaybeVarsAndGoal = ok4(Vars, StateVars, Goal, SubGoalWarningSpecs)
    else
        Specs = get_any_errors1(MaybeVars) ++
            get_any_errors_warnings2(MaybeGoal),
        MaybeVarsAndGoal = error4(Specs)
    ).

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

    % We parse goals in non-DCG contexts here, while we parse goals
    % in DCG contexts in parse_non_call_dcg_goal in parse_dcg_goal.m.
    %
    % Since many kinds of goals can occur in both kinds of contexts,
    % the code handling those kinds of goals should be kept as identical
    % as possible in the two places. For ease of maintenance, the switch arms
    % handling the common goal types should also be kept in the same order.
    % The list below documents the order in both predicates. If you add
    % code to handle a new goal type either here or there, please update
    % this comment.
    %
    % In both parse_non_call_goal and parse_non_call_dcg_goal:
    %
    %   impure/1, semipure/1
    %   promise_pure/1, promise_semipure/1, promise_impure/1
    %   disable_warnings/2
    %   not/1, \+/1
    %   some/2, all/2
    %   ,/2
    %   &/2
    %   ;/2                     (disjunction, or C->T;E style if-then-else)
    %   else                    (if C then T else E style if-then-else;
    %                               or try goal in parse_non_call_goal)
    %
    % Only in parse_non_call_goal, after the common goal types:
    %
    %   then/2                  (try goal)
    %   catch/2                 (try goal)
    %   catch_any/2             (try goal)
    %   <=/2, =>/2, <=>/2       (implication, bi-implication)
    %   trace/2                 (trace goals)
    %   atomic/2                (atomic goals)
    %   prom_eqv_{solns,sets}/2 (determinism cast)
    %   arbitrary/2             (determinism cast)
    %   req_{det,...}/2         (determinism check)
    %   req_compl_switch/2      (determinism check)
    %   req_sw_arms_{det,...}/2 (determinism check)
    %   event/2                 (debugger event)
    %   true/0, fail/0          (empty conjunction/disjunction)
    %   =/2, is/2               (unification)
    %
    % Only in parse_non_call_dcg_goal, after the common goal types:
    %
    %   if/2                    (if C then T, with implicit "else")
    %   {}/1                    (wrapping non-DCG goal)
    %   []/0                    (consuming nothing from dcg)
    %   [|]/2                   (consuming something from dcg)
    %   =/1                     (reading current dcg var)
    %   :=/1                    (writing to next dcg var)
    %
    % For the goal types that can occur in both DCG and non-DCG contexts,
    % the code handling them here and in parse_non_call_dcg_goal is often
    % very similar. These goal types are all compound, and they tend to differ
    % only in whether they call parse_goal or parse_dcg_goal to handle their
    % subgoals. However, factoring out the commonalities would nevertheless
    % not be a good idea, for two reasons. Both relate to the fact that
    % the common code would have to make a runtime decision between
    % calling parse_goal and parse_dcg_goal on terms representing subgoals,
    % and would have to pass around the arguments needed by parse_dcg_goal
    % to make the latter choice possible.
    %
    % The first reason is simply that with this extra generality,
    % the common code would not be significantly shorter OR simpler
    % than the two specialized pieces of code put together.
    %
    % The second reason is that the extra tests and parameter passing
    % would slow down the code in both contexts. For infrequently occurring
    % kinds of goals, this wouldn't matter, but conjunctions and if-then-elses
    % occur very frequently, and we don't want to pay any extra cost
    % for parsing them.

:- type goal_kind
    --->    gk_impure
    ;       gk_semipure
    ;       gk_promise_impure
    ;       gk_promise_semipure
    ;       gk_promise_pure
    ;       gk_disable_warning
    ;       gk_disable_warnings
    ;       gk_not
    ;       gk_not_prolog
    ;       gk_some
    ;       gk_all
    ;       gk_conj
    ;       gk_par_conj
    ;       gk_semicolon
    ;       gk_else
    ;       gk_if
    ;       gk_then
    ;       gk_catch
    ;       gk_catch_any
    ;       gk_imply_to_left
    ;       gk_imply_to_right
    ;       gk_imply_to_both
    ;       gk_trace
    ;       gk_atomic
    ;       gk_promise_eqv_solns
    ;       gk_promise_eqv_soln_sets
    ;       gk_arbitrary
    ;       gk_require_det
    ;       gk_require_semidet
    ;       gk_require_multi
    ;       gk_require_nondet
    ;       gk_require_cc_multi
    ;       gk_require_cc_nondet
    ;       gk_require_erroneous
    ;       gk_require_failure
    ;       gk_require_complete_switch
    ;       gk_require_arms_det
    ;       gk_require_arms_semidet
    ;       gk_require_arms_multi
    ;       gk_require_arms_nondet
    ;       gk_require_arms_cc_multi
    ;       gk_require_arms_cc_nondet
    ;       gk_require_arms_erroneous
    ;       gk_require_arms_failure
    ;       gk_event
    ;       gk_true
    ;       gk_fail
    ;       gk_equal.

:- inst goal_kind_purity for goal_kind/0
    --->    gk_impure
    ;       gk_semipure.

:- inst goal_kind_promise_purity for goal_kind/0
    --->    gk_promise_impure
    ;       gk_promise_semipure
    ;       gk_promise_pure.

:- inst goal_kind_disable_warning for goal_kind/0
    --->    gk_disable_warning
    ;       gk_disable_warnings.

:- inst goal_kind_not for goal_kind/0
    --->    gk_not
    ;       gk_not_prolog.

:- inst goal_kind_some_all for goal_kind/0
    --->    gk_some
    ;       gk_all.

:- inst goal_kind_conj for goal_kind/0
    --->    gk_conj.

:- inst goal_kind_par_conj for goal_kind/0
    --->    gk_par_conj.

:- inst goal_kind_implication for goal_kind/0
    --->    gk_imply_to_left
    ;       gk_imply_to_right
    ;       gk_imply_to_both.

:- inst goal_kind_promise_eqv_soln for goal_kind/0
    --->    gk_promise_eqv_solns
    ;       gk_promise_eqv_soln_sets.

:- inst goal_kind_require_detism for goal_kind/0
    --->    gk_require_det
    ;       gk_require_semidet
    ;       gk_require_multi
    ;       gk_require_nondet
    ;       gk_require_cc_multi
    ;       gk_require_cc_nondet
    ;       gk_require_erroneous
    ;       gk_require_failure.

:- inst goal_kind_require_arm_detism for goal_kind/0
    --->    gk_require_arms_det
    ;       gk_require_arms_semidet
    ;       gk_require_arms_multi
    ;       gk_require_arms_nondet
    ;       gk_require_arms_cc_multi
    ;       gk_require_arms_cc_nondet
    ;       gk_require_arms_erroneous
    ;       gk_require_arms_failure.

:- inst goal_kind_true_fail for goal_kind/0
    --->    gk_true
    ;       gk_fail.

:- pred string_goal_kind(string, goal_kind).
:- mode string_goal_kind(in, out) is semidet.
:- mode string_goal_kind(out, in) is det.

string_goal_kind(Functor, GoalKind) :-
    ( Functor = "impure",               GoalKind = gk_impure
    ; Functor = "semipure",             GoalKind = gk_semipure
    ; Functor = "promise_pure",         GoalKind = gk_promise_pure
    ; Functor = "promise_semipure",     GoalKind = gk_promise_semipure
    ; Functor = "promise_impure",       GoalKind = gk_promise_impure
    ; Functor = "disable_warning",      GoalKind = gk_disable_warning
    ; Functor = "disable_warnings",     GoalKind = gk_disable_warnings
    % Negation (NU-Prolog syntax).
    ; Functor = "not",                  GoalKind = gk_not
    % Negation (Prolog syntax).
    ; Functor = "\\+",                  GoalKind = gk_not_prolog
    ; Functor = "some",                 GoalKind = gk_some
    ; Functor = "all",                  GoalKind = gk_all
    ; Functor = ",",                    GoalKind = gk_conj
    ; Functor = "&",                    GoalKind = gk_par_conj
    ; Functor = ";",                    GoalKind = gk_semicolon
    % If-then-else (NU-Prolog syntax).
    ; Functor = "else",                 GoalKind = gk_else
    % If-then-else (NU-Prolog syntax) with a missing "else".
    ; Functor = "if",                   GoalKind = gk_if
    ; Functor = "then",                 GoalKind = gk_then
    ; Functor = "catch",                GoalKind = gk_catch
    ; Functor = "catch_any",            GoalKind = gk_catch_any
    ; Functor = "<=",                   GoalKind = gk_imply_to_left
    ; Functor = "=>",                   GoalKind = gk_imply_to_right
    ; Functor = "<=>",                  GoalKind = gk_imply_to_both
    ; Functor = "trace",                GoalKind = gk_trace
    ; Functor = "atomic",               GoalKind = gk_atomic
    ; Functor = "promise_equivalent_solutions",
                                        GoalKind = gk_promise_eqv_solns
    ; Functor = "promise_equivalent_solution_sets",
                                        GoalKind = gk_promise_eqv_soln_sets
    ; Functor = "arbitrary",            GoalKind = gk_arbitrary
    ; Functor = "require_det",          GoalKind = gk_require_det
    ; Functor = "require_semidet",      GoalKind = gk_require_semidet
    ; Functor = "require_multi",        GoalKind = gk_require_multi
    ; Functor = "require_nondet",       GoalKind = gk_require_nondet
    ; Functor = "require_cc_multi",     GoalKind = gk_require_cc_multi
    ; Functor = "require_cc_nondet",    GoalKind = gk_require_cc_nondet
    ; Functor = "require_erroneous",    GoalKind = gk_require_erroneous
    ; Functor = "require_failure",      GoalKind = gk_require_failure
    ; Functor = "require_complete_switch",
                                        GoalKind = gk_require_complete_switch
    ; Functor = "require_switch_arms_det",
                                        GoalKind = gk_require_arms_det
    ; Functor = "require_switch_arms_semidet",
                                        GoalKind = gk_require_arms_semidet
    ; Functor = "require_switch_arms_multi",
                                        GoalKind = gk_require_arms_multi
    ; Functor = "require_switch_arms_nondet",
                                        GoalKind = gk_require_arms_nondet
    ; Functor = "require_switch_arms_cc_multi",
                                        GoalKind = gk_require_arms_cc_multi
    ; Functor = "require_switch_arms_cc_nondet",
                                        GoalKind = gk_require_arms_cc_nondet
    ; Functor = "require_switch_arms_erroneous",
                                        GoalKind = gk_require_arms_erroneous
    ; Functor = "require_switch_arms_failure",
                                        GoalKind = gk_require_arms_failure
    ; Functor = "event",                GoalKind = gk_event
    ; Functor = "true",                 GoalKind = gk_true
    ; Functor = "fail",                 GoalKind = gk_fail
    ; Functor = "=",                    GoalKind = gk_equal
    ).

:- pred parse_non_call_goal(goal_kind::in, list(term)::in, term.context::in,
    cord(format_piece)::in, maybe2(goal, list(warning_spec))::out,
    prog_varset::in, prog_varset::out) is det.

parse_non_call_goal(GoalKind, Args, Context, ContextPieces, MaybeGoal,
        !VarSet) :-
    % XXX We should update ContextPieces at every call to parse a goal
    % component that is not itself a goal.
    (
        ( GoalKind = gk_impure
        ; GoalKind = gk_semipure
        ),
        parse_goal_impure_semipure(GoalKind, Args, Context, ContextPieces,
            MaybeGoal, !VarSet)
    ;
        ( GoalKind = gk_promise_pure
        ; GoalKind = gk_promise_semipure
        ; GoalKind = gk_promise_impure
        ),
        parse_goal_promise_purity(GoalKind, Args, Context, ContextPieces,
            MaybeGoal, !VarSet)
    ;
        ( GoalKind = gk_disable_warning
        ; GoalKind = gk_disable_warnings
        ),
        parse_goal_disable_warnings(GoalKind, Args, Context, ContextPieces,
            MaybeGoal, !VarSet)
    ;
        ( GoalKind = gk_not
        ; GoalKind = gk_not_prolog
        ),
        parse_goal_not(GoalKind, Args, Context, ContextPieces,
            MaybeGoal, !VarSet)
    ;
        ( GoalKind = gk_some
        ; GoalKind = gk_all
        ),
        parse_goal_some_all(GoalKind, Args, Context, ContextPieces,
            MaybeGoal, !VarSet)
    ;
        GoalKind = gk_conj,
        % We select the in(gk_conj) mode of parse_goal_conj.
        parse_goal_conj(GoalKind, Args, Context, ContextPieces,
            MaybeGoal, !VarSet)
    ;
        GoalKind = gk_par_conj,
        % We select the in(gk_par_conj) mode of parse_goal_conj.
        parse_goal_conj(GoalKind, Args, Context, ContextPieces,
            MaybeGoal, !VarSet)
    ;
        GoalKind = gk_semicolon,
        parse_goal_semicolon(Args, Context, ContextPieces, MaybeGoal, !VarSet)
    ;
        GoalKind = gk_else,
        % If-then-else (NU-Prolog syntax).
        parse_goal_else(Args, Context, ContextPieces, MaybeGoal, !VarSet)
    ;
        GoalKind = gk_if,
        parse_goal_if(Args, Context, ContextPieces, MaybeGoal, !VarSet)
    ;
        GoalKind = gk_then,
        parse_goal_then(Args, Context, ContextPieces, MaybeGoal, !VarSet)
    ;
        GoalKind = gk_catch,
        parse_catch_then_try_term_args(Args, no, Context, ContextPieces,
            MaybeGoal, !VarSet)
    ;
        GoalKind = gk_catch_any,
        parse_goal_catch_any(Args, Context, ContextPieces, MaybeGoal, !VarSet)
    ;
        ( GoalKind = gk_imply_to_left
        ; GoalKind = gk_imply_to_right
        ; GoalKind = gk_imply_to_both
        ),
        parse_goal_implication(GoalKind, Args, Context, ContextPieces,
            MaybeGoal, !VarSet)
    ;
        GoalKind = gk_trace,
        parse_goal_trace(Args, Context, ContextPieces, MaybeGoal, !VarSet)
    ;
        GoalKind = gk_atomic,
        parse_goal_atomic(Args, Context, ContextPieces, MaybeGoal, !VarSet)
    ;
        ( GoalKind = gk_promise_eqv_solns
        ; GoalKind = gk_promise_eqv_soln_sets
        ),
        parse_goal_promise_eqv_solns(GoalKind, Args, Context, ContextPieces,
            MaybeGoal, !VarSet)
    ;
        GoalKind = gk_arbitrary,
        parse_goal_arbitrary(Args, Context, ContextPieces, MaybeGoal, !VarSet)
    ;
        ( GoalKind = gk_require_det
        ; GoalKind = gk_require_semidet
        ; GoalKind = gk_require_multi
        ; GoalKind = gk_require_nondet
        ; GoalKind = gk_require_cc_multi
        ; GoalKind = gk_require_cc_nondet
        ; GoalKind = gk_require_erroneous
        ; GoalKind = gk_require_failure
        ),
        parse_goal_require_detism(GoalKind, Args, Context, ContextPieces,
            MaybeGoal, !VarSet)
    ;
        GoalKind = gk_require_complete_switch,
        parse_goal_require_complete_switch(Args, Context, ContextPieces,
            MaybeGoal, !VarSet)
    ;
        ( GoalKind = gk_require_arms_det
        ; GoalKind = gk_require_arms_semidet
        ; GoalKind = gk_require_arms_multi
        ; GoalKind = gk_require_arms_nondet
        ; GoalKind = gk_require_arms_cc_multi
        ; GoalKind = gk_require_arms_cc_nondet
        ; GoalKind = gk_require_arms_erroneous
        ; GoalKind = gk_require_arms_failure
        ),
        parse_goal_require_switch_arm_detism(GoalKind, Args,
            Context, ContextPieces, MaybeGoal, !VarSet)
    ;
        GoalKind = gk_event,
        parse_goal_event(Args, Context, ContextPieces, MaybeGoal, !VarSet)
    ;
        ( GoalKind = gk_true
        ; GoalKind = gk_fail
        ),
        parse_goal_true_fail(GoalKind, Args, Context, ContextPieces,
            MaybeGoal, !VarSet)
    ;
        GoalKind = gk_equal,
        parse_goal_equal(Args, Context, ContextPieces,
            MaybeGoal, !VarSet)
    ).

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

:- pred parse_goal_impure_semipure(goal_kind::in(goal_kind_purity),
    list(term)::in, term.context::in, cord(format_piece)::in,
    maybe2(goal, list(warning_spec))::out,
    prog_varset::in, prog_varset::out) is det.
:- pragma inline(pred(parse_goal_impure_semipure/7)).

parse_goal_impure_semipure(GoalKind, ArgTerms, Context, ContextPieces,
        MaybeGoal, !VarSet) :-
    ( GoalKind = gk_impure,   Purity = purity_impure
    ; GoalKind = gk_semipure, Purity = purity_semipure
    ),
    ( if ArgTerms = [SubGoalTerm] then
        parse_goal(SubGoalTerm, ContextPieces, MaybeGoal0, !VarSet),
        apply_purity_marker_to_maybe_goal(SubGoalTerm, Purity,
            MaybeGoal0, MaybeGoal)
    else
        string_goal_kind(Functor, GoalKind),
        Spec = should_have_one_goal_prefix(ContextPieces, Context, Functor),
        MaybeGoal = error2([Spec])
    ).

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

:- pred parse_goal_promise_purity(goal_kind::in(goal_kind_promise_purity),
    list(term)::in, term.context::in, cord(format_piece)::in,
    maybe2(goal, list(warning_spec))::out,
    prog_varset::in, prog_varset::out) is det.
:- pragma inline(pred(parse_goal_promise_purity/7)).

parse_goal_promise_purity(GoalKind, ArgTerms, Context, ContextPieces,
        MaybeGoal, !VarSet) :-
    ( GoalKind = gk_promise_pure,     Purity = purity_pure
    ; GoalKind = gk_promise_semipure, Purity = purity_semipure
    ; GoalKind = gk_promise_impure,   Purity = purity_impure
    ),
    ( if ArgTerms = [SubGoalTerm] then
        parse_goal(SubGoalTerm, ContextPieces, MaybeSubGoal, !VarSet),
        (
            MaybeSubGoal = ok2(SubGoal, SubGoalWarningSpecs),
            Goal = promise_purity_expr(Context, Purity, SubGoal),
            MaybeGoal = ok2(Goal, SubGoalWarningSpecs)
        ;
            MaybeSubGoal = error2(Specs),
            MaybeGoal = error2(Specs)
        )
    else
        string_goal_kind(Functor, GoalKind),
        Spec = should_have_one_goal_prefix(ContextPieces, Context, Functor),
        MaybeGoal = error2([Spec])
    ).

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

:- pred parse_goal_disable_warnings(goal_kind::in(goal_kind_disable_warning),
    list(term)::in, term.context::in, cord(format_piece)::in,
    maybe2(goal, list(warning_spec))::out,
    prog_varset::in, prog_varset::out) is det.
:- pragma inline(pred(parse_goal_disable_warnings/7)).

parse_goal_disable_warnings(GoalKind, ArgTerms, Context, ContextPieces,
        MaybeGoal, !VarSet) :-
    string_goal_kind(Functor, GoalKind),
    ( if ArgTerms = [WarningsTerm, SubGoalTerm] then
        varset.coerce(!.VarSet, GenericVarSet),
        parse_warnings(GenericVarSet, WarningsTerm, Functor,
            ContextPieces, 1, MaybeWarnings),
        parse_goal(SubGoalTerm, ContextPieces, MaybeSubGoal, !VarSet),
        ( if
            MaybeWarnings = ok2(Warnings, WarningsWarningSpecs),
            MaybeSubGoal = ok2(SubGoal, SubGoalWarningSpecs)
        then
            WarningSpecs = WarningsWarningSpecs ++ SubGoalWarningSpecs,
            WarningsContext = get_term_context(WarningsTerm),
            bag.insert_list(Warnings, bag.init, WarningsBag),
            bag.to_assoc_list(WarningsBag, WarningsCounts),
            generate_warnings_for_duplicate_warnings(WarningsContext,
                ContextPieces, WarningsCounts,
                NonDuplicateWarnings, DuplicateSpecs),
            (
                DuplicateSpecs = [],
                (
                    NonDuplicateWarnings = [HeadWarning | TailWarnings],
                    Goal = disable_warnings_expr(Context,
                        HeadWarning, TailWarnings, SubGoal),
                    MaybeGoal = ok2(Goal, WarningSpecs)
                ;
                    NonDuplicateWarnings = [],
                    (
                        WarningsWarningSpecs = [],
                        Pieces = cord.list(ContextPieces) ++
                            [lower_case_next_if_not_first,
                            words("Error:"), words("a")] ++
                            color_as_subject([fixed(Functor),
                                words("scope")]) ++
                            color_as_incorrect([words("must list"),
                                words("at least one warning.")]) ++
                            [nl],
                        Spec = spec($pred, severity_error, phase_t2pt,
                            WarningsContext, Pieces),
                        MaybeGoal = error2([Spec | WarningSpecs])
                    ;
                        WarningsWarningSpecs = [_ | _],
                        % We get here if WarningsTerm is a well formed list
                        % but contains only elements that are *not*
                        % warning names. Generating the error message
                        % immediately above would be misleading, since
                        % the user seemingly *did* try to put a warning
                        % in the warning list, he/she just failed at it.
                        % But we don't have any valid warnings for a
                        % disable_warnings scope either. In this case, we just
                        % forgo constructing a scope that would have no effect.
                        MaybeGoal = ok2(SubGoal, WarningSpecs)
                    )
                )
            ;
                DuplicateSpecs = [_ | _],
                MaybeGoal = error2(DuplicateSpecs ++ WarningSpecs)
            )
        else
            Specs = get_any_errors_warnings2(MaybeWarnings) ++
                get_any_errors_warnings2(MaybeSubGoal),
            MaybeGoal = error2(Specs)
        )
    else
        Spec = should_have_one_x_one_goal_prefix(ContextPieces, Context,
            "a list of warnings to disable", Functor),
        MaybeGoal = error2([Spec])
    ).

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

:- pred parse_goal_not(goal_kind::in(goal_kind_not), list(term)::in,
    term.context::in, cord(format_piece)::in,
    maybe2(goal, list(warning_spec))::out,
    prog_varset::in, prog_varset::out) is det.
:- pragma inline(pred(parse_goal_not/7)).

parse_goal_not(GoalKind, ArgTerms, Context, ContextPieces, MaybeGoal,
        !VarSet) :-
    ( if ArgTerms = [SubGoalTerm] then
        parse_goal(SubGoalTerm, ContextPieces, MaybeSubGoal, !VarSet),
        (
            MaybeSubGoal = ok2(SubGoal, SubGoalWarningSpecs),
            Goal = not_expr(Context, SubGoal),
            MaybeGoal = ok2(Goal, SubGoalWarningSpecs)
        ;
            MaybeSubGoal = error2(Specs),
            MaybeGoal = error2(Specs)
        )
    else
        string_goal_kind(Functor, GoalKind),
        Spec = should_have_one_goal_prefix(ContextPieces, Context, Functor),
        MaybeGoal = error2([Spec])
    ).

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

:- pred parse_goal_some_all(goal_kind::in(goal_kind_some_all),
    list(term)::in, term.context::in, cord(format_piece)::in,
    maybe2(goal, list(warning_spec))::out,
    prog_varset::in, prog_varset::out) is det.
:- pragma inline(pred(parse_goal_some_all/7)).

parse_goal_some_all(GoalKind, ArgTerms, Context, ContextPieces,
        MaybeGoal, !VarSet) :-
    (
        GoalKind = gk_some,
        Functor = "some",
        QuantType = quant_some,
        VarsTailPieces = [lower_case_next_if_not_first,
            words("In first argument of"), quote("some"), suffix(":")]
    ;
        GoalKind = gk_all,
        Functor = "all",
        QuantType = quant_all,
        VarsTailPieces = [lower_case_next_if_not_first,
            words("In first argument of"), quote("all"), suffix(":")]
    ),
    ( if ArgTerms = [QVarsTerm, SubGoalTerm] then
        varset.coerce(!.VarSet, GenericVarSet),
        VarsContextPieces = ContextPieces ++ cord.from_list(VarsTailPieces),
        parse_vars_state_vars(QVarsTerm, GenericVarSet, VarsContextPieces,
            MaybeVars),
        parse_goal(SubGoalTerm, ContextPieces, MaybeSubGoal, !VarSet),
        ( if
            MaybeVars = ok1(plain_state_vars(Vars0, StateVars0)),
            MaybeSubGoal = ok2(SubGoal, SubGoalWarningSpecs)
        then
            list.map(term.coerce_var, Vars0, Vars),
            list.map(term.coerce_var, StateVars0, StateVars),
            (
                StateVars = [],
                Goal1 = SubGoal
            ;
                StateVars = [_ | _],
                Goal1 = quant_expr(QuantType, quant_state_vars, Context,
                    StateVars, SubGoal)
            ),
            (
                Vars = [],
                Goal = Goal1
            ;
                Vars = [_ | _],
                Goal = quant_expr(QuantType, quant_ordinary_vars, Context,
                    Vars, Goal1)
            ),
            MaybeGoal = ok2(Goal, SubGoalWarningSpecs)
        else
            Specs = get_any_errors1(MaybeVars) ++
                get_any_errors_warnings2(MaybeSubGoal),
            MaybeGoal = error2(Specs)
        )
    else
        Spec = should_have_one_x_one_goal_prefix(ContextPieces, Context,
            "a list of variables", Functor),
        MaybeGoal = error2([Spec])
    ).

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

    % Although we do almost exactly the same thing for "&" as for ",",
    % we handle them in separate modes, because "," is FAR more common
    % than "&", and keeping its processing efficient is important enough
    % to warrant a small amount of target language code duplication.
    %
:- pred parse_goal_conj(goal_kind, list(term),
    term.context, cord(format_piece),
    maybe2(goal, list(warning_spec)), prog_varset, prog_varset).
:- mode parse_goal_conj(in(goal_kind_conj),
    in, in, in, out, in, out) is det.
:- mode parse_goal_conj(in(goal_kind_par_conj),
    in, in, in, out, in, out) is det.
:- pragma inline(pred(parse_goal_conj/7)).

parse_goal_conj(GoalKind, ArgTerms, Context, ContextPieces,
        MaybeGoal, !VarSet) :-
    string_goal_kind(Functor, GoalKind),
    ( if ArgTerms = [SubGoalTermA, SubGoalTermB] then
        parse_goal_conjunction(Functor, SubGoalTermA, SubGoalTermB,
            ContextPieces, cord.init, ConjunctsCord, [], WarningSpecs,
            [], ErrorSpecs, !VarSet),
        (
            ErrorSpecs = [],
            Conjuncts = cord.list(ConjunctsCord),
            (
                Conjuncts = [],
                unexpected($pred, "no Conjuncts")
            ;
                Conjuncts = [Conjunct1 | Conjuncts2plus]
            ),
            (
                GoalKind = gk_conj,
                Goal = conj_expr(Context, Conjunct1, Conjuncts2plus)
            ;
                GoalKind = gk_par_conj,
                Goal = par_conj_expr(Context, Conjunct1, Conjuncts2plus)
            ),
            MaybeGoal = ok2(Goal, WarningSpecs)
        ;
            ErrorSpecs = [_ | _],
            MaybeGoal = error2(ErrorSpecs)
        )
    else
        Spec = should_have_two_goals_infix(ContextPieces, Context, Functor),
        MaybeGoal = error2([Spec])
    ).

:- pred parse_goal_conjunction(string::in, term::in, term::in,
    cord(format_piece)::in, cord(goal)::in, cord(goal)::out,
    list(warning_spec)::in, list(warning_spec)::out,
    list(error_spec)::in, list(error_spec)::out,
    prog_varset::in, prog_varset::out) is det.
% Don't inline this predicate, since it is recursive.

parse_goal_conjunction(Functor, TermA, TermB, ContextPieces, !ConjunctsCord,
        !Warnings, !Specs, !VarSet) :-
    parse_goal(TermA, ContextPieces, MaybeGoalA, !VarSet),
    (
        MaybeGoalA = ok2(ConjunctA, WarningsA),
        cord.snoc(ConjunctA, !ConjunctsCord),
        !:Warnings = WarningsA ++ !.Warnings
    ;
        MaybeGoalA = error2(SpecsA),
        !:Specs = !.Specs ++ SpecsA
    ),
    ( if
        TermB = term.functor(term.atom(Functor), ArgTermsB, _Context),
        ArgTermsB = [TermBA, TermBB]
    then
        parse_goal_conjunction(Functor, TermBA, TermBB, ContextPieces,
            !ConjunctsCord, !Warnings, !Specs, !VarSet)
    else
        parse_goal(TermB, ContextPieces, MaybeGoalB, !VarSet),
        (
            MaybeGoalB = ok2(ConjunctB, WarningsB),
            cord.snoc(ConjunctB, !ConjunctsCord),
            !:Warnings = !.Warnings ++ WarningsB
        ;
            MaybeGoalB = error2(SpecsB),
            !:Specs = !.Specs ++ SpecsB
        )
    ).

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

:- pred parse_goal_semicolon(list(term)::in,
    term.context::in, cord(format_piece)::in,
    maybe2(goal, list(warning_spec))::out,
    prog_varset::in, prog_varset::out) is det.
:- pragma inline(pred(parse_goal_semicolon/6)).

parse_goal_semicolon(ArgTerms, Context, ContextPieces, MaybeGoal, !VarSet) :-
    ( if ArgTerms = [SubGoalTermA, SubGoalTermB] then
        ( if
            SubGoalTermA = term.functor(term.atom("->"),
                [CondGoalTerm, ThenGoalTerm], _)
        then
            ElseGoalTerm = SubGoalTermB,
            parse_some_vars_goal(CondGoalTerm, ContextPieces, MaybeCondGoal,
                !VarSet),
            parse_goal(ThenGoalTerm, ContextPieces, MaybeThenGoal, !VarSet),
            parse_goal(ElseGoalTerm, ContextPieces, MaybeElseGoal, !VarSet),
            ( if
                MaybeCondGoal =
                    ok4(Vars, StateVars, CondGoal, CondWarningSpecs),
                MaybeThenGoal = ok2(ThenGoal, ThenWarningSpecs),
                MaybeElseGoal = ok2(ElseGoal, ElseWarningSpecs)
            then
                Goal = if_then_else_expr(Context, Vars, StateVars,
                    CondGoal, ThenGoal, ElseGoal),
                WarningSpecs = CondWarningSpecs ++
                    ThenWarningSpecs ++ ElseWarningSpecs,
                MaybeGoal = ok2(Goal, WarningSpecs)
            else
                Specs = get_any_errors_warnings4(MaybeCondGoal) ++
                    get_any_errors_warnings2(MaybeThenGoal) ++
                    get_any_errors_warnings2(MaybeElseGoal),
                MaybeGoal = error2(Specs)
            )
        else
            parse_goal_disjunction(SubGoalTermA, SubGoalTermB, ContextPieces,
                cord.init, DisjunctsCord, [], WarningSpecs, [], ErrorSpecs,
                !VarSet),
            (
                ErrorSpecs = [],
                Disjuncts = cord.list(DisjunctsCord),
                (
                    ( Disjuncts = []
                    ; Disjuncts = [_]
                    ),
                    unexpected($pred, "less than two disjuncts")
                ;
                    Disjuncts = [Disjunct1, Disjunct2 | Disjuncts3plus]
                ),
                Goal = disj_expr(Context, Disjunct1, Disjunct2,
                    Disjuncts3plus),
                MaybeGoal = ok2(Goal, WarningSpecs)
            ;
                ErrorSpecs = [_ | _],
                MaybeGoal = error2(ErrorSpecs)
            )
        )
    else
        % XXX This generates an error message that is appropriate for goals
        % that are intended to be disjunctions. Should we instead generate
        % a message that also talks about if-then-elses using (C->T;E) syntax?
        % It would be more complete, but also more complex, and therefore
        % potentially more confusing than helpful.
        % We do the same for ";" in parse_non_call_dcg_goal.
        Spec = should_have_two_goals_infix(ContextPieces, Context, ";"),
        MaybeGoal = error2([Spec])
    ).

:- pred parse_goal_disjunction(term::in, term::in, cord(format_piece)::in,
    cord(goal)::in, cord(goal)::out,
    list(warning_spec)::in, list(warning_spec)::out,
    list(error_spec)::in, list(error_spec)::out,
    prog_varset::in, prog_varset::out) is det.
% Don't inline this predicate, since it is recursive.

parse_goal_disjunction(TermA, TermB, ContextPieces, !DisjunctsCord,
        !Warnings, !Specs, !VarSet) :-
    parse_goal(TermA, ContextPieces, MaybeGoalA, !VarSet),
    (
        MaybeGoalA = ok2(DisjunctA, WarningsA),
        append_disjunct_to_cord(DisjunctA, !DisjunctsCord),
        % The order of the warnings does not matter.
        !:Warnings = WarningsA ++ !.Warnings
    ;
        MaybeGoalA = error2(SpecsA),
        !:Specs = !.Specs ++ SpecsA
    ),
    ( if
        TermB = term.functor(term.atom(";"), ArgTermsB, _Context),
        ArgTermsB = [TermBA, TermBB],
        not (
            TermBA = term.functor(term.atom("->"), [_, _], _)
        )
    then
        parse_goal_disjunction(TermBA, TermBB, ContextPieces, !DisjunctsCord,
            !Warnings, !Specs, !VarSet)
    else
        parse_goal(TermB, ContextPieces, MaybeGoalB, !VarSet),
        (
            MaybeGoalB = ok2(DisjunctB, WarningsB),
            cord.snoc(DisjunctB, !DisjunctsCord),
            !:Warnings = !.Warnings ++ WarningsB
        ;
            MaybeGoalB = error2(SpecsB),
            !:Specs = !.Specs ++ SpecsB
        )
    ).

:- pred append_disjunct_to_cord(goal::in,
    cord(goal)::in, cord(goal)::out) is det.

append_disjunct_to_cord(Goal, !DisjunctsCord) :-
    % We flatten disjunctions, for reasons explained in the comment
    % at the top of tests/hard_coded/flatten_disjunctions.m.
    ( if Goal = disj_expr(_Ctxt, Disjunct1, Disjunct2, Disjuncts3plus) then
        append_disjunct_to_cord(Disjunct1, !DisjunctsCord),
        append_disjunct_to_cord(Disjunct2, !DisjunctsCord),
        list.foldl(append_disjunct_to_cord, Disjuncts3plus, !DisjunctsCord)
    else
        cord.snoc(Goal, !DisjunctsCord)
    ).

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

:- pred parse_goal_else(list(term)::in,
    term.context::in, cord(format_piece)::in,
    maybe2(goal, list(warning_spec))::out,
    prog_varset::in, prog_varset::out) is det.
:- pragma inline(pred(parse_goal_else/6)).

parse_goal_else(ArgTerms, Context, ContextPieces, MaybeGoal, !VarSet) :-
    ( if ArgTerms = [CondThenTerm, ElseTerm] then
        ( if
            CondThenTerm = term.functor(term.atom("if"),
                [term.functor(term.atom("then"), [CondTerm, ThenTerm], _)],
                CondContext)
        then
            parse_some_vars_goal(CondTerm, ContextPieces, MaybeCondGoal,
                !VarSet),
            parse_goal(ThenTerm, ContextPieces, MaybeThenGoal, !VarSet),
            parse_goal(ElseTerm, ContextPieces, MaybeElseGoal, !VarSet),
            ( if
                MaybeCondGoal =
                    ok4(Vars, StateVars, CondGoal, CondWarningSpecs),
                MaybeThenGoal = ok2(ThenGoal, ThenWarningSpecs),
                MaybeElseGoal = ok2(ElseGoal, ElseWarningSpecs)
            then
                Goal = if_then_else_expr(CondContext, Vars, StateVars,
                    CondGoal, ThenGoal, ElseGoal),
                WarningSpecs = CondWarningSpecs ++
                    ThenWarningSpecs ++ ElseWarningSpecs,
                MaybeGoal = ok2(Goal, WarningSpecs)
            else
                Specs = get_any_errors_warnings4(MaybeCondGoal) ++
                    get_any_errors_warnings2(MaybeThenGoal) ++
                    get_any_errors_warnings2(MaybeElseGoal),
                MaybeGoal = error2(Specs)
            )
        else if
            CondThenTerm = term.functor(term.atom("if"),
                [term.functor(term.atom("->"),
                    [_CondGoalTerm, _ThenGoalTerm], ArrowContext)],
                _CondContext)
        then
            Pieces = [words("Error: malformed if-then-else;")] ++
                color_as_incorrect([words("replace the"), quote("->"),
                    words("with"), quote("then"), suffix(".")]) ++
                [nl],
            Spec = spec($pred, severity_error, phase_t2pt,
                ArrowContext, Pieces),
            MaybeGoal = error2([Spec])
        else if
            CondThenTerm = term.functor(term.atom("->"),
                [_CondGoalTerm, _ThenGoalTerm], ArrowContext)
        then
            Pieces = [words("Error: malformed if-then-else;")] ++
                color_as_incorrect([words("replace the"), quote("->"),
                    words("with"), quote("then"), suffix(","),
                    words("and add an"), quote("if"),
                    words("before the condition.")]) ++
                [nl],
            Spec = spec($pred, severity_error, phase_t2pt,
                ArrowContext, Pieces),
            MaybeGoal = error2([Spec])
        else
            % `else' can also be part of a `try' goal.
            FullTerm = term.functor(term.atom("else"),
                [CondThenTerm, ElseTerm], Context),
            parse_else_then_try_term(FullTerm, [], no, Context, ContextPieces,
                MaybeGoal, !VarSet)
        )
    else
        % XXX This generates an error message that is appropriate for goals
        % that are intended to be if-then-elses. Should we instead generate
        % a message that also talks about try goals? It would be more complete,
        % but also more complex, and therefore more likely to be confusing
        % than helpful, since try goals are *much* rarer than if-then-elses.
        Pieces = [words("Error: the")] ++
            color_as_subject([quote("else"), words("operator")]) ++
            color_as_incorrect(
                [words("should occur in expressions of the form")]) ++
            color_as_correct([quote("( if goal then goal else goal )"),
                suffix(".")]) ++
            [nl],
        Spec = spec($pred, severity_error, phase_t2pt, Context, Pieces),
        MaybeGoal = error2([Spec])
    ).

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

:- pred parse_goal_if(list(term)::in,
    term.context::in, cord(format_piece)::in,
    maybe2(goal, list(warning_spec))::out,
    prog_varset::in, prog_varset::out) is det.
:- pragma inline(pred(parse_goal_if/6)).

parse_goal_if(ArgTerms, Context, _ContextPieces, MaybeGoal, !VarSet) :-
    ( if
        ArgTerms = [term.functor(term.atom("then"),
            [_CondGoalTerm, ThenGoalTerm], ThenContext)]
    then
        ( if
            ThenGoalTerm = term.functor(term.atom(";"),
                [_, _], SemiColonContext)
        then
            Pieces =
                [words("Error: malformed if-then-else;")] ++
                color_as_incorrect([words("replace the"), quote(";"),
                words("with"), quote("else"), suffix(".")]) ++
                [nl],
            Spec = spec($pred, severity_error, phase_t2pt,
                SemiColonContext, Pieces)
        else
            Pieces =
                [words("Error: malformed if-then-else;")] ++
                color_as_incorrect([words("this"), quote("then"),
                    words("has no"), quote("else"), suffix(".")]) ++
                [nl],
            Spec = spec($pred, severity_error, phase_t2pt, ThenContext, Pieces)
        )
    else
        Pieces =
            [words("Error: malformed if-then-else;")] ++
            color_as_incorrect([words("this"), quote("if"),
                words("has no"), quote("then"), suffix(".")]) ++
            [nl],
        Spec = spec($pred, severity_error, phase_t2pt, Context, Pieces)
    ),
    MaybeGoal = error2([Spec]).

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

:- pred parse_goal_then(list(term)::in,
    term.context::in, cord(format_piece)::in,
    maybe2(goal, list(warning_spec))::out,
    prog_varset::in, prog_varset::out) is det.
:- pragma inline(pred(parse_goal_then/6)).

parse_goal_then(ArgTerms, Context, ContextPieces, MaybeGoal, !VarSet) :-
    ( if ArgTerms = [TryTerm, ThenTerm] then
        ( if
            ThenTerm = term.functor(term.atom(";"), [_, _], SemiColonContext)
        then
            % The term we are parsing is not a valid try goal.
            % It is much more likely to be a malformed if-then-else
            % than a malformed try goal, so generate an error message
            % that is more informative in the common case.
            Pieces =
                [words("Error: malformed if-then-else;")] ++
                color_as_incorrect([words("replace the"), quote(";"),
                    words("with"), quote("else"), suffix(".")]) ++
                [nl],
            Spec = spec($pred, severity_error, phase_t2pt,
                SemiColonContext, Pieces),
            MaybeGoal = error2([Spec])
        else
            parse_then_try_term(
                term.functor(atom("then"), [TryTerm, ThenTerm], Context),
                no, [], no, Context, ContextPieces, MaybeGoal, !VarSet)
        )
    else
        % Since there was no "else" wrapped around this use of "then",
        % it is quite likely that this may have been intended to be a try goal.
        % XXX Should we list all the things that may follow
        % the initial part of a try goal?
        Pieces =
            [words("Error: the ")] ++
            color_as_subject([quote("then"), words("operator,")]) ++
            color_as_incorrect([words("should be used")]) ++
            [words("either in an expression of the form")] ++
            color_as_correct([quote("( if goal then goal else goal )"),
                suffix(",")]) ++
            [words("or in an expression of the form")] ++
            color_as_correct(
                [quote("try [try_params] main_goal then success_goal"),
                suffix(",")]) ++
            [words("optionally followed by")] ++
            color_as_correct([quote("else failure_goal"), suffix(",")]) ++
            [words("which in turn may be followed by zero or more"),
            quote("catch"), words("clauses, and optionally by a single"),
            quote("catch_any"), words("clause."), nl],
        Spec = spec($pred, severity_error, phase_t2pt, Context, Pieces),
        MaybeGoal = error2([Spec])
    ).

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

:- pred parse_goal_catch_any(list(term)::in,
    term.context::in, cord(format_piece)::in,
    maybe2(goal, list(warning_spec))::out,
    prog_varset::in, prog_varset::out) is det.
:- pragma inline(pred(parse_goal_catch_any/6)).

parse_goal_catch_any(ArgTerms, Context, ContextPieces, MaybeGoal, !VarSet) :-
    ( if ArgTerms = [TermA, ArrowTerm] then
        parse_catch_any_term(ArrowTerm, Context, ContextPieces,
            MaybeCatchAnyExpr, !VarSet),
        (
            MaybeCatchAnyExpr = ok2(CatchAnyExpr, CatchWarningSpecs),
            ( if TermA = term.functor(atom("catch"), TermAArgs, _) then
                parse_catch_then_try_term_args(TermAArgs, yes(CatchAnyExpr),
                    Context, ContextPieces, MaybeGoal0, !VarSet)
            else
                parse_else_then_try_term(TermA, [], yes(CatchAnyExpr),
                    Context, ContextPieces, MaybeGoal0, !VarSet)
            ),
            (
                MaybeGoal0 = ok2(Goal, GoalWarningSpecs),
                MaybeGoal = ok2(Goal, CatchWarningSpecs ++ GoalWarningSpecs)
            ;
                MaybeGoal0 = error2(Specs),
                MaybeGoal = error2(CatchWarningSpecs ++ Specs)
            )
        ;
            MaybeCatchAnyExpr = error2(Specs),
            MaybeGoal = error2(Specs)
        )
    else
        Pieces = [words("Error: the")] ++
            color_as_subject([quote("catch_any"), words("operator")]) ++
            color_as_incorrect([words("should be preceded by")]) ++
            [words("a try expression, with a then-clause,"),
            words("optional else-clause and zero or more catch clauses,"),
            words("and should be followed by an expression of the form"),
            quote("variable -> goal"), suffix("."), nl],
        Spec = spec($pred, severity_error, phase_t2pt, Context, Pieces),
        MaybeGoal = error2([Spec])
    ).

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

:- pred parse_goal_implication(goal_kind::in(goal_kind_implication),
    list(term)::in, term.context::in, cord(format_piece)::in,
    maybe2(goal, list(warning_spec))::out,
    prog_varset::in, prog_varset::out) is det.
:- pragma inline(pred(parse_goal_implication/7)).

parse_goal_implication(GoalKind, ArgTerms, Context, ContextPieces,
        MaybeGoal, !VarSet) :-
    ( if ArgTerms = [TermA, TermB] then
        parse_goal(TermA, ContextPieces, MaybeGoalA, !VarSet),
        parse_goal(TermB, ContextPieces, MaybeGoalB, !VarSet),
        ( if
            MaybeGoalA = ok2(GoalA, GoalWarningSpecsA),
            MaybeGoalB = ok2(GoalB, GoalWarningSpecsB)
        then
            (
                GoalKind = gk_imply_to_left,
                Goal = implies_expr(Context, GoalB, GoalA)
            ;
                GoalKind = gk_imply_to_right,
                Goal = implies_expr(Context, GoalA, GoalB)
            ;
                GoalKind = gk_imply_to_both,
                Goal = equivalent_expr(Context, GoalA, GoalB)
            ),
            WarningSpecs = GoalWarningSpecsA ++ GoalWarningSpecsB,
            MaybeGoal = ok2(Goal, WarningSpecs)
        else
            Specs = get_any_errors_warnings2(MaybeGoalA) ++
                get_any_errors_warnings2(MaybeGoalB),
            MaybeGoal = error2(Specs)
        )
    else
        string_goal_kind(Functor, GoalKind),
        Spec = should_have_two_goals_infix(ContextPieces, Context, Functor),
        MaybeGoal = error2([Spec])
    ).

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

:- pred parse_goal_trace(list(term)::in,
    term.context::in, cord(format_piece)::in,
    maybe2(goal, list(warning_spec))::out,
    prog_varset::in, prog_varset::out) is det.
:- pragma inline(pred(parse_goal_trace/6)).

parse_goal_trace(ArgTerms, Context, ContextPieces, MaybeGoal, !VarSet) :-
    ( if ArgTerms = [ParamsTerm, SubGoalTerm] then
        varset.coerce(!.VarSet, GenericVarSet),
        parse_trace_params(GenericVarSet, Context, ParamsTerm, MaybeParams),
        parse_goal(SubGoalTerm, ContextPieces, MaybeSubGoal, !VarSet),
        ( if
            MaybeParams = ok1(Params),
            MaybeSubGoal = ok2(SubGoal, SubGoalWarningSpecs)
        then
            convert_trace_params(Params, MaybeComponents),
            (
                MaybeComponents = ok4(CompileTime, RunTime, MaybeIO, MutVars),
                Goal = trace_expr(Context, CompileTime, RunTime,
                    MaybeIO, MutVars, SubGoal),
                MaybeGoal = ok2(Goal, SubGoalWarningSpecs)
            ;
                MaybeComponents = error4(Specs),
                MaybeGoal = error2(Specs ++ SubGoalWarningSpecs)
            )
        else
            Specs = get_any_errors1(MaybeParams) ++
                get_any_errors_warnings2(MaybeSubGoal),
            MaybeGoal = error2(Specs)
        )
    else
        Spec = should_have_one_x_one_goal_prefix(ContextPieces, Context,
            "a list of trace parameters", "trace"),
        MaybeGoal = error2([Spec])
    ).

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

:- pred parse_goal_atomic(list(term)::in,
    term.context::in, cord(format_piece)::in,
    maybe2(goal, list(warning_spec))::out,
    prog_varset::in, prog_varset::out) is det.
:- pragma inline(pred(parse_goal_atomic/6)).

parse_goal_atomic(ArgTerms, Context, ContextPieces, MaybeGoal, !VarSet) :-
    ( if ArgTerms = [ParamsTerm, SubGoalsTerm] then
        varset.coerce(!.VarSet, GenericVarSet),
        parse_atomic_params(Context, ParamsTerm, GenericVarSet, MaybeParams),
        parse_atomic_subgoals(SubGoalsTerm, MaybeSubGoals, !VarSet),
        ( if
            MaybeParams = ok1(Params),
            MaybeSubGoals = ok3(MainGoal, OrElseGoals, SubGoalWarningSpecs)
        then
            convert_atomic_params(ParamsTerm, Params, MaybeComponents),
            (
                MaybeComponents = ok3(Outer, Inner, MaybeOutputVars),
                Goal = atomic_expr(Context, Outer, Inner, MaybeOutputVars,
                    MainGoal, OrElseGoals),
                MaybeGoal = ok2(Goal, SubGoalWarningSpecs)
            ;
                MaybeComponents = error3(Specs),
                MaybeGoal = error2(Specs ++ SubGoalWarningSpecs)
            )
        else
            Specs = get_any_errors1(MaybeParams) ++
                get_any_errors_warnings3(MaybeSubGoals),
            MaybeGoal = error2(Specs)
        )
    else
        Spec = should_have_one_x_one_goal_prefix(ContextPieces, Context,
            "a list of atomic parameters", "atomic"),
        MaybeGoal = error2([Spec])
    ).

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

:- pred parse_goal_promise_eqv_solns(
    goal_kind::in(goal_kind_promise_eqv_soln),
    list(term)::in, term.context::in, cord(format_piece)::in,
    maybe2(goal, list(warning_spec))::out,
    prog_varset::in, prog_varset::out) is det.
:- pragma inline(pred(parse_goal_promise_eqv_solns/7)).

parse_goal_promise_eqv_solns(GoalKind, ArgTerms, Context, ContextPieces,
        MaybeGoal, !VarSet) :-
    ( if ArgTerms = [VarsTerm, SubGoalTerm] then
        varset.coerce(!.VarSet, GenericVarSet),
        parse_vars_state_dot_colon_vars(VarsTerm, GenericVarSet,
            ContextPieces, MaybeVars),
        parse_goal(SubGoalTerm, ContextPieces, MaybeSubGoal, !VarSet),
        ( if
            MaybeVars = ok1(plain_state_dot_colon_vars(Vars0,
                StateVars0, DotSVars0, ColonSVars0)),
            MaybeSubGoal = ok2(SubGoal, SubGoalWarningSpecs)
        then
            list.map(term.coerce_var, Vars0, Vars),
            list.map(term.coerce_var, StateVars0, StateVars),
            list.map(term.coerce_var, DotSVars0, DotSVars),
            list.map(term.coerce_var, ColonSVars0, ColonSVars),
            (
                GoalKind = gk_promise_eqv_solns,
                Goal = promise_equivalent_solutions_expr(Context, Vars,
                    StateVars, DotSVars, ColonSVars, SubGoal),
                MaybeGoal = ok2(Goal, SubGoalWarningSpecs)
            ;
                GoalKind = gk_promise_eqv_soln_sets,
                Goal = promise_equivalent_solution_sets_expr(Context, Vars,
                    StateVars, DotSVars, ColonSVars, SubGoal),
                MaybeGoal = ok2(Goal, SubGoalWarningSpecs)
            )
        else
            Specs = get_any_errors1(MaybeVars) ++
                get_any_errors_warnings2(MaybeSubGoal),
            MaybeGoal = error2(Specs)
        )
    else
        string_goal_kind(Functor, GoalKind),
        Spec = should_have_one_x_one_goal_prefix(ContextPieces, Context,
            "a list of variables", Functor),
        MaybeGoal = error2([Spec])
    ).

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

:- pred parse_goal_arbitrary(list(term)::in,
    term.context::in, cord(format_piece)::in,
    maybe2(goal, list(warning_spec))::out,
    prog_varset::in, prog_varset::out) is det.
:- pragma inline(pred(parse_goal_arbitrary/6)).

parse_goal_arbitrary(ArgTerms, Context, ContextPieces, MaybeGoal, !VarSet) :-
    ( if ArgTerms = [VarsTerm, SubGoalTerm] then
        varset.coerce(!.VarSet, GenericVarSet),
        parse_vars_state_dot_colon_vars(VarsTerm, GenericVarSet,
            ContextPieces, MaybeVars),
        parse_goal(SubGoalTerm, ContextPieces, MaybeSubGoal, !VarSet),
        ( if
            MaybeVars = ok1(plain_state_dot_colon_vars(Vars0,
                StateVars0, DotSVars0, ColonSVars0)),
            MaybeSubGoal = ok2(SubGoal, SubGoalWarningSpecs)
        then
            list.map(term.coerce_var, Vars0, Vars),
            list.map(term.coerce_var, StateVars0, StateVars),
            list.map(term.coerce_var, DotSVars0, DotSVars),
            list.map(term.coerce_var, ColonSVars0, ColonSVars),
            Goal = promise_equivalent_solution_arbitrary_expr(Context,
                Vars, StateVars, DotSVars, ColonSVars, SubGoal),
            MaybeGoal = ok2(Goal, SubGoalWarningSpecs)
        else
            Specs = get_any_errors1(MaybeVars) ++
                get_any_errors_warnings2(MaybeSubGoal),
            MaybeGoal = error2(Specs)
        )
    else
        Spec = should_have_one_x_one_goal_prefix(ContextPieces, Context,
            "a list of variables", "arbitrary"),
        MaybeGoal = error2([Spec])
    ).

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

:- pred parse_goal_require_detism(goal_kind::in(goal_kind_require_detism),
    list(term)::in, term.context::in, cord(format_piece)::in,
    maybe2(goal, list(warning_spec))::out,
    prog_varset::in, prog_varset::out) is det.
:- pragma inline(pred(parse_goal_require_detism/7)).

parse_goal_require_detism(GoalKind, ArgTerms, Context, ContextPieces,
        MaybeGoal, !VarSet) :-
    ( GoalKind = gk_require_det,          Detism = detism_det
    ; GoalKind = gk_require_semidet,      Detism = detism_semi
    ; GoalKind = gk_require_multi,        Detism = detism_multi
    ; GoalKind = gk_require_nondet,       Detism = detism_non
    ; GoalKind = gk_require_cc_multi,     Detism = detism_cc_multi
    ; GoalKind = gk_require_cc_nondet,    Detism = detism_cc_non
    ; GoalKind = gk_require_erroneous,    Detism = detism_erroneous
    ; GoalKind = gk_require_failure,      Detism = detism_failure
    ),
    ( if ArgTerms = [SubGoalTerm] then
        parse_goal(SubGoalTerm, ContextPieces, MaybeSubGoal, !VarSet),
        (
            MaybeSubGoal = ok2(SubGoal, SubGoalWarningSpecs),
            Goal = require_detism_expr(Context, Detism, SubGoal),
            MaybeGoal = ok2(Goal, SubGoalWarningSpecs)
        ;
            MaybeSubGoal = error2(Specs),
            MaybeGoal = error2(Specs)
        )
    else
        string_goal_kind(Functor, GoalKind),
        Spec = should_have_one_goal_prefix(ContextPieces, Context, Functor),
        MaybeGoal = error2([Spec])
    ).

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

:- pred parse_goal_require_complete_switch(list(term)::in,
    term.context::in, cord(format_piece)::in,
    maybe2(goal, list(warning_spec))::out,
    prog_varset::in, prog_varset::out) is det.
:- pragma inline(pred(parse_goal_require_complete_switch/6)).

parse_goal_require_complete_switch(ArgTerms, Context, ContextPieces,
        MaybeGoal, !VarSet) :-
    ( if ArgTerms = [VarsTerm, SubGoalTerm] then
        term.coerce(VarsTerm, ProgVarsTerm),
        parse_vars_state_dot_colon_vars(ProgVarsTerm, !.VarSet,
            ContextPieces, MaybePSDCVars),
        parse_goal(SubGoalTerm, ContextPieces, MaybeSubGoal, !VarSet),
        ( if
            MaybePSDCVars = ok1(PSDCVars0),
            MaybeSubGoal = ok2(SubGoal, SubGoalWarningSpecs)
        then
            parse_one_plain_or_dot_var(PSDCVars0, SubGoal,
                ContextPieces, "require_complete_switch", MaybePODVar),
            (
                MaybePODVar = ok1(PODVar),
                Goal = require_complete_switch_expr(Context, PODVar, SubGoal),
                MaybeGoal = ok2(Goal, SubGoalWarningSpecs)
            ;
                MaybePODVar = error1(RCSSpecs),
                MaybeGoal = error2(RCSSpecs ++ SubGoalWarningSpecs)
            )
        else
            Specs = get_any_errors1(MaybePSDCVars) ++
                get_any_errors_warnings2(MaybeSubGoal),
            MaybeGoal = error2(Specs)
        )
    else
        Spec = should_have_one_x_one_goal_prefix(ContextPieces, Context,
            "a variable in a singleton list", "require_complete_switch"),
        MaybeGoal = error2([Spec])
    ).

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

:- pred parse_goal_require_switch_arm_detism(
    goal_kind::in(goal_kind_require_arm_detism),
    list(term)::in, term.context::in, cord(format_piece)::in,
    maybe2(goal, list(warning_spec))::out,
    prog_varset::in, prog_varset::out) is det.
:- pragma inline(pred(parse_goal_require_switch_arm_detism/7)).

parse_goal_require_switch_arm_detism(GoalKind, ArgTerms,
        Context, ContextPieces, MaybeGoal, !VarSet) :-
    (
        GoalKind = gk_require_arms_det,
        Detism = detism_det,
        Functor = "require_switch_arms_det"
    ;
        GoalKind = gk_require_arms_semidet,
        Detism = detism_semi,
        Functor = "require_switch_arms_semidet"
    ;
        GoalKind = gk_require_arms_multi,
        Detism = detism_multi,
        Functor = "require_switch_arms_multi"
    ;
        GoalKind = gk_require_arms_nondet,
        Detism = detism_non,
        Functor = "require_switch_arms_nondet"
    ;
        GoalKind = gk_require_arms_cc_multi,
        Detism = detism_cc_multi,
        Functor = "require_switch_arms_cc_multi"
    ;
        GoalKind = gk_require_arms_cc_nondet,
        Detism = detism_cc_non,
        Functor = "require_switch_arms_cc_nondet"
    ;
        GoalKind = gk_require_arms_erroneous,
        Detism = detism_erroneous,
        Functor = "require_switch_arms_erroneous"
    ;
        GoalKind = gk_require_arms_failure,
        Detism = detism_failure,
        Functor = "require_switch_arms_failure"
    ),
    ( if ArgTerms = [VarsTerm, SubGoalTerm] then
        term.coerce(VarsTerm, ProgVarsTerm),
        parse_vars_state_dot_colon_vars(ProgVarsTerm, !.VarSet,
            ContextPieces, MaybePSDCVars),
        parse_goal(SubGoalTerm, ContextPieces, MaybeSubGoal, !VarSet),
        ( if
            MaybePSDCVars = ok1(PSDCVars0),
            MaybeSubGoal = ok2(SubGoal, SubGoalWarningSpecs)
        then
            parse_one_plain_or_dot_var(PSDCVars0, SubGoal,
                ContextPieces, Functor, MaybePODVar),
            (
                MaybePODVar = ok1(PODVar),
                Goal = require_switch_arms_detism_expr(Context, PODVar,
                    Detism, SubGoal),
                MaybeGoal = ok2(Goal, SubGoalWarningSpecs)
            ;
                MaybePODVar = error1(RCSSpecs),
                MaybeGoal = error2(RCSSpecs ++ SubGoalWarningSpecs)
            )
        else
            Specs = get_any_errors1(MaybePSDCVars) ++
                get_any_errors_warnings2(MaybeSubGoal),
            MaybeGoal = error2(Specs)
        )
    else
        Spec = should_have_one_x_one_goal_prefix(ContextPieces, Context,
            "a variable in a singleton list", Functor),
        MaybeGoal = error2([Spec])
    ).

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

:- pred parse_goal_event(list(term)::in,
    term.context::in, cord(format_piece)::in,
    maybe2(goal, list(warning_spec))::out,
    prog_varset::in, prog_varset::out) is det.
:- pragma inline(pred(parse_goal_event/6)).

parse_goal_event(ArgTerms, Context, ContextPieces, MaybeGoal, !VarSet) :-
    ( if ArgTerms = [SubGoalTerm] then
        parse_goal(SubGoalTerm, ContextPieces, MaybeSubGoal, !VarSet),
        (
            MaybeSubGoal = ok2(SubGoal, SubGoalWarningSpecs),
            ( if
                SubGoal = call_expr(SubContext, SymName, CallArgs, Purity)
            then
                ( if
                    SymName = unqualified(EventName),
                    Purity = purity_pure
                then
                    Goal = event_expr(Context, EventName, CallArgs),
                    MaybeGoal = ok2(Goal, SubGoalWarningSpecs)
                else
                    some [!Specs] (
                        !:Specs = [],
                        (
                            SymName = unqualified(_)
                        ;
                            SymName = qualified(_, _),
                            QualPieces = cord.list(ContextPieces) ++
                                [lower_case_next_if_not_first,
                                words("Error: the")] ++
                                color_as_subject([words("event name")]) ++
                                color_as_incorrect(
                                    [words("must not be qualified.")]) ++
                                [nl],
                            QualSpec = spec($pred, severity_error, phase_t2pt,
                                SubContext, QualPieces),
                            !:Specs = [QualSpec | !.Specs]
                        ),
                        (
                            Purity = purity_pure
                        ;
                            ( Purity = purity_semipure
                            ; Purity = purity_impure
                            ),
                            PurityPieces = cord.list(ContextPieces) ++
                                [lower_case_next_if_not_first,
                                words("Error:")] ++
                                color_as_incorrect([words("an event cannot"),
                                    words("be impure or semipure.")]) ++
                                [nl],
                            PuritySpec = spec($pred, severity_error,
                                phase_t2pt, SubContext, PurityPieces),
                            !:Specs = [PuritySpec | !.Specs]
                        ),
                        MaybeGoal = error2(!.Specs ++ SubGoalWarningSpecs)
                    )
                )
            else
                Spec = should_have_one_call_prefix(ContextPieces, Context,
                    "event"),
                MaybeGoal = error2([Spec | SubGoalWarningSpecs])
            )
        ;
            MaybeSubGoal = error2(Specs),
            MaybeGoal = error2(Specs)
        )
    else
        Spec = should_have_one_call_prefix(ContextPieces, Context, "event"),
        MaybeGoal = error2([Spec])
    ).

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

:- pred parse_goal_true_fail(goal_kind::in(goal_kind_true_fail),
    list(term)::in, term.context::in, cord(format_piece)::in,
    maybe2(goal, list(warning_spec))::out,
    prog_varset::in, prog_varset::out) is det.
:- pragma inline(pred(parse_goal_true_fail/7)).

parse_goal_true_fail(GoalKind, ArgTerms, Context, ContextPieces,
        MaybeGoal, !VarSet) :-
    (
        GoalKind = gk_true,
        Goal = true_expr(Context)
    ;
        GoalKind = gk_fail,
        Goal = fail_expr(Context)
    ),
    (
        ArgTerms = [],
        MaybeGoal = ok2(Goal, [])
    ;
        ArgTerms = [_ | _],
        string_goal_kind(Functor, GoalKind),
        Spec = should_have_no_args(ContextPieces, Context, Functor),
        MaybeGoal = error2([Spec])
    ).

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

:- pred parse_goal_equal(list(term)::in, term.context::in,
    cord(format_piece)::in, maybe2(goal, list(warning_spec))::out,
    prog_varset::in, prog_varset::out) is det.
:- pragma inline(pred(parse_goal_equal/6)).

parse_goal_equal(ArgTerms, Context, ContextPieces, MaybeGoal, !VarSet) :-
    ( if ArgTerms = [TermA0, TermB0] then
        term.coerce(TermA0, TermA),
        term.coerce(TermB0, TermB),
        MaybeGoal = ok2(unify_expr(Context, TermA, TermB, purity_pure), [])
    else
        Spec = should_have_two_terms_infix(ContextPieces, Context, "="),
        MaybeGoal = error2([Spec])
    ).

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

:- pred parse_one_plain_or_dot_var(
    plain_state_dot_colon_vars(prog_var_type)::in, goal::in,
    cord(format_piece)::in, string::in, maybe1(plain_or_dot_var)::out)
    is det.

parse_one_plain_or_dot_var(PSDCVars, Goal, ContextPieces, ConstructName,
        MaybePODVar) :-
    PSDCVars = plain_state_dot_colon_vars(PlainVars, StateVars,
        DotVars, ColonVars),
    Context = get_goal_context(Goal),
    (
        StateVars = [],
        MaybeStateVars = ok1(unit)
    ;
        StateVars = [_ | _],
        StatePieces = cord.list(ContextPieces) ++
            [words("Error: the")] ++
            color_as_subject([words("first argument of"),
                words(ConstructName)]) ++
            color_as_incorrect([words("may not contain")]) ++
            [words("a state variable pair."), nl],
        StateSpec = spec($pred, severity_error, phase_t2pt,
            Context, StatePieces),
        MaybeStateVars = error1([StateSpec])
    ),
    (
        ColonVars = [],
        MaybeColonVars = ok1(unit)
    ;
        ColonVars = [_ | _],
        ColonPieces = cord.list(ContextPieces) ++
            [words("Error: the")] ++
            color_as_subject([words("first argument of"),
                words(ConstructName)]) ++
            color_as_incorrect([words("may not contain")]) ++
            [words("a reference to the next value of a state variable."), nl],
        ColonSpec = spec($pred, severity_error, phase_t2pt,
            Context, ColonPieces),
        MaybeColonVars = error1([ColonSpec])
    ),
    (
        (
            PlainVars = [],
            MaybeMaybePlainVar = ok1(no)
        ;
            PlainVars = [PlainVar0],
            MaybeMaybePlainVar = ok1(yes(PlainVar0))
        )
    ;
        PlainVars = [_, _ | _],
        PlainPieces = cord.list(ContextPieces) ++
            [words("Error: the")] ++
            color_as_subject([words("first argument of"),
                words(ConstructName)]) ++
            color_as_incorrect([words("may not contain")]) ++
            [words("more than one variable."), nl],
        PlainSpec = spec($pred, severity_error, phase_t2pt,
            Context, PlainPieces),
        MaybeMaybePlainVar = error1([PlainSpec])
    ),
    (
        (
            DotVars = [],
            MaybeMaybeDotVar = ok1(no)
        ;
            DotVars = [DotVar0],
            MaybeMaybeDotVar = ok1(yes(DotVar0))
        )
    ;
        DotVars = [_, _ | _],
        DotPieces = cord.list(ContextPieces) ++
            [words("Error: the")] ++
            color_as_subject([words("first argument of"),
                words(ConstructName)]) ++
            color_as_incorrect([words("may not contain")]) ++
            [words("more than one variable."), nl],
        DotSpec = spec($pred, severity_error, phase_t2pt,
            Context, DotPieces),
        MaybeMaybeDotVar = error1([DotSpec])
    ),
    ( if
        MaybeStateVars = ok1(_),
        MaybeColonVars = ok1(_),
        MaybeMaybePlainVar = ok1(MaybePlainVar),
        MaybeMaybeDotVar = ok1(MaybeDotVar)
    then
        (
            MaybePlainVar = no,
            MaybeDotVar = no,
            Pieces = cord.list(ContextPieces) ++
                [words("Error: the")] ++
                color_as_subject([words("first argument of"),
                    words(ConstructName)]) ++
                color_as_incorrect([words("must contain")]) ++
                [words("a variable."), nl],
            Spec = spec($pred, severity_error, phase_t2pt, Context, Pieces),
            MaybePODVar = error1([Spec])
        ;
            MaybePlainVar = yes(PlainVar),
            MaybeDotVar = no,
            MaybePODVar = ok1(podv_plain(PlainVar))
        ;
            MaybePlainVar = no,
            MaybeDotVar = yes(DotVar),
            MaybePODVar = ok1(podv_dot(DotVar))
        ;
            MaybePlainVar = yes(_),
            MaybeDotVar = yes(_),
            Pieces = cord.list(ContextPieces) ++
                [words("Error: the")] ++
                color_as_subject([words("first argument of"),
                    words(ConstructName)]) ++
                color_as_incorrect([words("may not contain")]) ++
                [words("more than one variable."), nl],
            Spec = spec($pred, severity_error, phase_t2pt, Context, Pieces),
            MaybePODVar = error1([Spec])
        )
    else
        Specs =
            get_any_errors1(MaybeStateVars) ++
            get_any_errors1(MaybeColonVars) ++
            get_any_errors1(MaybeMaybePlainVar) ++
            get_any_errors1(MaybeMaybeDotVar),
        MaybePODVar = error1(Specs)
    ).

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

:- pred parse_warnings(varset::in, term::in, string::in,
    cord(format_piece)::in, int::in,
    maybe2(list(goal_warning), list(warning_spec))::out) is det.

parse_warnings(VarSet, Term, ScopeFunctor, ContextPieces, WarningNum,
        MaybeWarnings) :-
    ( if Term = term.functor(term.atom("[]"), [], _) then
        MaybeWarnings = ok2([], [])
    else if Term = term.functor(term.atom("[|]"), [HeadTerm, TailTerm], _) then
        parse_warning(VarSet, HeadTerm, ScopeFunctor, ContextPieces,
            WarningNum, HeadWarnings, HeadWarningSpecs),
        parse_warnings(VarSet, TailTerm, ScopeFunctor, ContextPieces,
            WarningNum + 1, MaybeTailWarnings),
        (
            MaybeTailWarnings = ok2(TailWarnings, TailWarningSpecs),
            Warnings = HeadWarnings ++ TailWarnings,
            WarningSpecs = HeadWarningSpecs ++ TailWarningSpecs,
            MaybeWarnings = ok2(Warnings, WarningSpecs)
        ;
            MaybeTailWarnings = error2(TailSpecs),
            Specs = HeadWarningSpecs ++ TailSpecs,
            MaybeWarnings = error2(Specs)
        )
    else
        TermStr = describe_error_term(VarSet, Term),
        Pieces = cord.list(ContextPieces) ++
            [lower_case_next_if_not_first, words("Error: after the"),
            quote(ScopeFunctor), words("keyword"), suffix(":"),
            words("expected a")] ++
            color_as_correct([words("list of warnings to disable,")]) ++
            [words("got")] ++
            color_as_incorrect([quote(TermStr), suffix(".")]) ++
            [nl],
        Spec = spec($pred, severity_error, phase_t2pt,
            get_term_context(Term), Pieces),
        MaybeWarnings = error2([Spec])
    ).

:- pred parse_warning(varset::in, term::in, string::in,
    cord(format_piece)::in, int::in,
    list(goal_warning)::out, list(warning_spec)::out) is det.

parse_warning(VarSet, Term, ScopeFunctor, ContextPieces, WarningNum,
        Warnings, WarningSpecs) :-
    ( if
        Term = term.functor(term.atom(WarningFunctor), [], _),
        (
            WarningFunctor = "non_tail_recursive_calls",
            Warning = goal_warning_non_tail_recursive_calls
        ;
            WarningFunctor = "suspected_occurs_check_failure",
            Warning = goal_warning_occurs_check
        ;
            WarningFunctor = "singleton_vars",
            Warning = goal_warning_singleton_vars
        ;
            WarningFunctor = "repeated_singleton_vars",
            Warning = goal_warning_repeated_singleton_vars
        ;
            WarningFunctor = "suspicious_recursion",
            Warning = goal_warning_suspicious_recursion
        ;
            WarningFunctor = "no_solution_disjunct",
            Warning = goal_warning_no_solution_disjunct
        ;
            WarningFunctor = "unknown_format_calls",
            Warning = goal_warning_unknown_format_calls
        )
    then
        Warnings = [Warning],
        WarningSpecs = []
    else
        TermStr = describe_error_term(VarSet, Term),
        Pieces = cord.list(ContextPieces) ++
            [lower_case_next_if_not_first, words("Warning:"),
            words("in the"), nth_fixed(WarningNum), words("element"),
            words("of the list following the"), quote(ScopeFunctor),
            words("keyword: expected the")] ++
            color_as_correct([words("the name of a warning,")]) ++
            [words("got")] ++
            color_as_incorrect([quote(TermStr), suffix(",")]) ++
            [nl],
        Spec = spec($pred, severity_warning, phase_t2pt,
            get_term_context(Term), Pieces),
        Warnings = [],
        WarningSpecs = [Spec]
    ).

:- pred generate_warnings_for_duplicate_warnings(prog_context::in,
    cord(format_piece)::in, assoc_list(goal_warning, int)::in,
    list(goal_warning)::out, list(error_spec)::out) is det.

generate_warnings_for_duplicate_warnings(_, _, [], [], []).
generate_warnings_for_duplicate_warnings(Context, ContextPieces,
        [WarningCount | WarningsCounts], NonDupWarnings, DupSpecs) :-
    generate_warnings_for_duplicate_warnings(Context, ContextPieces,
        WarningsCounts, TailNonDupWarnings, TailDupSpecs),
    WarningCount = Warning - Count,
    ( if Count > 1 then
        WarningStr = goal_warning_to_string(Warning),
        ( if Count = 2 then
            NTimesPieces = [words("twice")]
        else
            NTimesPieces = [int_fixed(Count), words("times")]
        ),
        Pieces = cord.list(ContextPieces) ++
            [lower_case_next_if_not_first, words("Error:"),
            words("the warning")] ++
            color_as_subject([fixed(WarningStr)]) ++
            [words("is")] ++
            color_as_incorrect([words("listed")] ++ NTimesPieces ++
                [suffix(".")]) ++
            [nl],
        Spec = spec($pred, severity_error, phase_t2pt, Context, Pieces),

        NonDupWarnings = TailNonDupWarnings,
        DupSpecs = [Spec | TailDupSpecs]
    else
        NonDupWarnings = [Warning | TailNonDupWarnings],
        DupSpecs = TailDupSpecs
    ).

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

:- type trace_component
    --->    trace_component_compiletime(trace_expr(trace_compiletime))
    ;       trace_component_runtime(trace_expr(trace_runtime))
    ;       trace_component_maybe_io(prog_var)
    ;       trace_component_mutable_var(trace_mutable_var).

:- pred parse_trace_params(varset::in, context::in, term::in,
    maybe1(assoc_list(trace_component, term.context))::out) is det.

parse_trace_params(VarSet, Context, Term, MaybeComponentsContexts) :-
    ( if Term = term.functor(term.atom("[]"), [], _) then
        MaybeComponentsContexts = ok1([])
    else if Term = term.functor(term.atom("[|]"), [HeadTerm, TailTerm], _) then
        parse_trace_component(VarSet, Term, HeadTerm,
            MaybeHeadComponentContext),
        parse_trace_params(VarSet, Context, TailTerm,
            MaybeTailComponentsContexts),
        ( if
            MaybeHeadComponentContext = ok1(HeadComponentContext),
            MaybeTailComponentsContexts = ok1(TailComponentsContexts)
        then
            MaybeComponentsContexts =
                ok1([HeadComponentContext | TailComponentsContexts])
        else
            Specs = get_any_errors1(MaybeHeadComponentContext) ++
                get_any_errors1(MaybeTailComponentsContexts),
            MaybeComponentsContexts = error1(Specs)
        )
    else
        TermStr = describe_error_term(VarSet, Term),
        Pieces = [words("Error: expected a")] ++
            color_as_correct([words("list of trace goal parameters,")]) ++
            [words("got")] ++
            color_as_incorrect([quote(TermStr), suffix(".")]) ++
            [nl],
        Spec = spec($pred, severity_error, phase_t2pt,
            get_term_context(Term), Pieces),
        MaybeComponentsContexts = error1([Spec])
    ).

:- pred parse_trace_component(varset::in, term::in, term::in,
    maybe1(pair(trace_component, term.context))::out) is det.

parse_trace_component(VarSet, _ErrorTerm, Term, MaybeComponentContext) :-
    ( if
        Term = term.functor(Functor, SubTerms, Context),
        Functor = term.atom(Atom)
    then
        ( if
            ( Atom = "compiletime"
            ; Atom = "compile_time"
            )
        then
            ( if SubTerms = [SubTerm] then
                parse_trace_tree(parse_trace_compiletime(VarSet), SubTerm,
                    MaybeCompileTime),
                (
                    MaybeCompileTime = ok1(CompileTime),
                    Component = trace_component_compiletime(CompileTime),
                    MaybeComponentContext = ok1(Component - Context)
                ;
                    MaybeCompileTime = error1(Specs),
                    MaybeComponentContext = error1(Specs)
                )
            else
                Pieces = [words("Error:")] ++
                    color_as_subject([fixed(Atom)]) ++
                    color_as_subject(
                        [words("should have exactly one argument,")]) ++
                    [words("which should be a boolean expression"),
                    words("of compile-time tests."), nl],
                Spec = spec($pred, severity_error, phase_t2pt,
                    Context, Pieces),
                MaybeComponentContext = error1([Spec])
            )
        else if
            ( Atom = "runtime"
            ; Atom = "run_time"
            )
        then
            ( if SubTerms = [SubTerm] then
                parse_trace_tree(parse_trace_runtime(VarSet), SubTerm,
                    MaybeRunTime),
                (
                    MaybeRunTime = ok1(RunTime),
                    Component = trace_component_runtime(RunTime),
                    MaybeComponentContext = ok1(Component - Context)
                ;
                    MaybeRunTime = error1(Specs),
                    MaybeComponentContext = error1(Specs)
                )
            else
                Pieces = [words("Error:")] ++
                    color_as_subject([fixed(Atom)]) ++
                    color_as_incorrect(
                        [words("should have exactly one argument,")])++
                    [words("which should be a boolean expression"),
                    words("of run-time tests."), nl],
                Spec = spec($pred, severity_error, phase_t2pt,
                    Context, Pieces),
                MaybeComponentContext = error1([Spec])
            )
        else if
            Atom = "io"
        then
            ( if SubTerms = [SubTerm] then
                ( if
                    SubTerm = term.functor(term.atom("!"),
                        [term.variable(Var, _)], _)
                then
                    term.coerce_var(Var, ProgVar),
                    Component = trace_component_maybe_io(ProgVar),
                    MaybeComponentContext = ok1(Component - Context)
                else
                    Pieces = [words("Error: the argument of")] ++
                        color_as_subject([fixed(Atom)]) ++
                        color_as_incorrect(
                            [words("should be a state variable.")]) ++
                        [nl],
                    Spec = spec($pred, severity_error, phase_t2pt,
                        get_term_context(SubTerm), Pieces),
                    MaybeComponentContext = error1([Spec])
                )
            else
                Pieces = [words("Error:")] ++
                    color_as_subject([fixed(Atom)]) ++
                    color_as_incorrect(
                        [words("should have exactly one argument,")]) ++
                    [words("which should be a state variable name."), nl],
                Spec = spec($pred, severity_error, phase_t2pt,
                    Context, Pieces),
                MaybeComponentContext = error1([Spec])
            )
        else if
            Atom = "state"
        then
            ( if SubTerms = [SubTermA, SubTermB] then
                ( if
                    SubTermA = term.functor(term.atom(MutableName), [], _)
                then
                    MaybeMutable = ok1(MutableName)
                else
                    MutablePieces = [words("Error: the")] ++
                        color_as_subject([words("first argument of"),
                            fixed(Atom)]) ++
                        color_as_incorrect([words("should be"),
                            words("the name of a mutable variable.")]) ++
                        [nl],
                    MutableSpec = spec($pred, severity_error, phase_t2pt,
                        get_term_context(SubTermA), MutablePieces),
                    MaybeMutable = error1([MutableSpec])
                ),
                ( if
                    SubTermB = term.functor(term.atom("!"),
                        [term.variable(Var, _)], _)
                then
                    MaybeVar = ok1(Var)
                else
                    VarPieces = [words("Error: the")] ++
                        color_as_subject([words("second argument of"),
                            fixed(Atom)]) ++
                        color_as_incorrect([words("should be"),
                            words("a state variable name.")]) ++
                        [nl],
                    VarSpec = spec($pred, severity_error, phase_t2pt,
                        get_term_context(SubTermB), VarPieces),
                    MaybeVar = error1([VarSpec])
                ),
                ( if
                    MaybeMutable = ok1(FinalMutable),
                    MaybeVar = ok1(FinalVar)
                then
                    term.coerce_var(FinalVar, ProgVar),
                    MutableVar = trace_mutable_var(FinalMutable, ProgVar),
                    Component = trace_component_mutable_var(MutableVar),
                    MaybeComponentContext = ok1(Component - Context)
                else
                    Specs = get_any_errors1(MaybeVar) ++
                        get_any_errors1(MaybeMutable),
                    MaybeComponentContext = error1(Specs)
                )
            else
                Pieces = [words("Error:")] ++
                    color_as_subject([fixed(Atom)]) ++
                    color_as_incorrect(
                        [words("should have exactly two arguments,")]) ++
                    [words("which should be"),
                    words("the name of a mutable variable"),
                    words("and a state variable name."), nl],
                Spec = spec($pred, severity_error, phase_t2pt,
                    Context, Pieces),
                MaybeComponentContext = error1([Spec])
            )
        else
            TermStr = describe_error_term(VarSet, Term),
            Pieces = [words("Error: expected a")] ++
                color_as_correct([words("trace goal parameter,")]) ++
                [words("got")] ++
                color_as_incorrect([quote(TermStr), suffix(".")]) ++
                [nl],
            Spec = spec($pred, severity_error, phase_t2pt, Context, Pieces),
            MaybeComponentContext = error1([Spec])
        )
    else
        TermStr = describe_error_term(VarSet, Term),
        Pieces = [words("Error: expected a")] ++
            color_as_correct([words("trace goal parameter,")]) ++
            [words("got")] ++
            color_as_incorrect([quote(TermStr), suffix(".")]) ++
            [nl],
        Spec = spec($pred, severity_error, phase_t2pt,
            get_term_context(Term), Pieces),
        MaybeComponentContext = error1([Spec])
    ).

:- pred parse_trace_tree(pred(term, maybe1(T))::in(pred(in, out) is det),
    term::in, maybe1(trace_expr(T))::out) is det.

parse_trace_tree(BaseParser, Term, MaybeTree) :-
    ( if
        Term = term.functor(term.atom(Atom), [LTerm, RTerm], _),
        (
            Atom = "or",
            Op = trace_or
        ;
            Atom = "and",
            Op = trace_and
        )
    then
        parse_trace_tree(BaseParser, LTerm, MaybeLExpr),
        parse_trace_tree(BaseParser, RTerm, MaybeRExpr),
        ( if
            MaybeLExpr = ok1(LExpr),
            MaybeRExpr = ok1(RExpr)
        then
            MaybeTree = ok1(trace_op(Op, LExpr, RExpr))
        else
            Specs = get_any_errors1(MaybeLExpr) ++
                get_any_errors1(MaybeRExpr),
            MaybeTree = error1(Specs)
        )
    else if
        Term = term.functor(term.atom("not"), [SubTerm], _)
    then
        parse_trace_tree(BaseParser, SubTerm, MaybeSubExpr),
        ( if
            MaybeSubExpr = ok1(SubExpr)
        then
            MaybeTree = ok1(trace_not(SubExpr))
        else
            SubSpecs = get_any_errors1(MaybeSubExpr),
            MaybeTree = error1(SubSpecs)
        )
    else
        BaseParser(Term, MaybeBase),
        (
            MaybeBase = ok1(Base),
            MaybeTree = ok1(trace_base(Base))
        ;
            MaybeBase = error1(Specs),
            MaybeTree = error1(Specs)
        )
    ).

:- pred parse_trace_compiletime(varset::in, term::in,
    maybe1(trace_compiletime)::out) is det.

parse_trace_compiletime(VarSet, Term, MaybeCompiletime) :-
    ( if
        Term = term.functor(Functor, SubTerms, TermContext),
        Functor = term.atom(Atom)
    then
        ( if Atom = "flag" then
            ( if SubTerms = [SubTerm] then
                ( if SubTerm = term.functor(term.string(FlagName), [], _) then
                    Compiletime = trace_flag(FlagName),
                    MaybeCompiletime = ok1(Compiletime)
                else
                    SubTermStr = describe_error_term(VarSet, SubTerm),
                    Pieces = [words("Error: expexted a")] ++
                        color_as_correct([words("string")]) ++
                        [words("as the argument of"), quote("flag"),
                            suffix(","), words("got")] ++
                        color_as_incorrect([quote(SubTermStr), suffix(".")]) ++
                        [nl],
                    Spec = spec($pred, severity_error, phase_t2pt,
                        TermContext, Pieces),
                    MaybeCompiletime = error1([Spec])
                )
            else
                Pieces = [words("Error: compile_time parameter")] ++
                    color_as_subject([quote("flag")]) ++
                    color_as_incorrect(
                        [words("should have just one argument.")]) ++
                    [nl],
                Spec = spec($pred, severity_error, phase_t2pt,
                    TermContext, Pieces),
                MaybeCompiletime = error1([Spec])
            )
        else if Atom = "grade" then
            ( if SubTerms = [SubTerm] then
                ( if
                    SubTerm = term.functor(term.atom(GradeName), [], _),
                    parse_trace_grade_name(GradeName, TraceGrade)
                then
                    Compiletime = trace_grade(TraceGrade),
                    MaybeCompiletime = ok1(Compiletime)
                else
                    SubTermStr = describe_error_term(VarSet, SubTerm),
                    solutions(valid_trace_grade_name, ValidGradeNames),
                    Pieces = [words("Error:")] ++
                        color_as_subject([quote(SubTermStr)]) ++
                        color_as_incorrect(
                            [words("is not a valid grade test.")]) ++
                        [words("The valid grade tests are")] ++
                        fixed_list_to_color_pieces(color_correct, "and",
                            [suffix(".")], ValidGradeNames) ++
                        [nl],
                    Spec = spec($pred, severity_error, phase_t2pt,
                        TermContext, Pieces),
                    MaybeCompiletime = error1([Spec])
                )
            else
                Pieces = [words("Error: compile_time parameter"),
                    quote("grade"), words("takes just one argument."), nl],
                Spec = spec($pred, severity_error, phase_t2pt,
                    TermContext, Pieces),
                MaybeCompiletime = error1([Spec])
            )
        else if ( Atom = "tracelevel" ; Atom = "trace_level" ) then
            ( if SubTerms = [SubTerm] then
                ( if
                    SubTerm = term.functor(term.atom(LevelName), [], _),
                    (
                        LevelName = "shallow",
                        Level = trace_level_shallow
                    ;
                        LevelName = "deep",
                        Level = trace_level_deep
                    )
                then
                    Compiletime = trace_trace_level(Level),
                    MaybeCompiletime = ok1(Compiletime)
                else
                    SubTermStr = describe_error_term(VarSet, SubTerm),
                    Pieces = [words("Error:")] ++
                        color_as_incorrect([quote(SubTermStr)]) ++
                        color_as_incorrect(
                            [words("is not a valid trace level.")]) ++
                        [words("The valid trace levels are")] ++
                        color_as_correct([quote("shallow")]) ++
                        [words("and")] ++
                        color_as_correct([quote("deep"), suffix(".")]) ++
                        [nl],
                    Spec = spec($pred, severity_error, phase_t2pt,
                        TermContext, Pieces),
                    MaybeCompiletime = error1([Spec])
                )
            else
                Pieces = [words("Error: compile_time parameter")] ++
                    color_as_subject([quote(Atom)]) ++
                    color_as_incorrect(
                        [words("should have just one argument.")]) ++
                    [nl],
                Spec = spec($pred, severity_error, phase_t2pt,
                    TermContext, Pieces),
                MaybeCompiletime = error1([Spec])
            )
        else
            TermStr = describe_error_term(VarSet, Term),
            Form1 = "flag(""name of --trace-flag parameter"")",
            Form2 = "grade(<grade test>)",
            Form3 = "tracelevel(shallow)",
            Form4 = "tracelevel(deep)",
            Pieces = [words("Error:")] ++
                color_as_subject([quote(TermStr)]) ++
                color_as_incorrect(
                    [words("is not a valid compile_time parameter.")]) ++
                [words("The valid compile_time paramaters"),
                words("have one of the following forms:"),
                nl_indent_delta(1)] ++
                color_as_correct([quote(Form1)]) ++ [nl] ++
                color_as_correct([quote(Form2)]) ++ [nl] ++
                color_as_correct([quote(Form3)]) ++ [nl] ++
                color_as_correct([quote(Form4)]) ++
                [nl_indent_delta(-1)],
            Spec = spec($pred, severity_error, phase_t2pt,
                TermContext, Pieces),
            MaybeCompiletime = error1([Spec])
        )
    else
        TermStr = describe_error_term(VarSet, Term),
        Form1 = "flag(""name of --trace-flag parameter"")",
        Form2 = "grade(<grade test>)",
        Form3 = "tracelevel(shallow)",
        Form4 = "tracelevel(deep)",
        Pieces = [words("Error:")] ++
            color_as_subject([quote(TermStr)]) ++
            color_as_incorrect(
                [words("is not a valid compile_time parameter.")]) ++
            [words("The valid compile_time paramaters"),
            words("have one of the following forms:"),
            nl_indent_delta(1)] ++
            color_as_correct([quote(Form1)]) ++ [nl] ++
            color_as_correct([quote(Form2)]) ++ [nl] ++
            color_as_correct([quote(Form3)]) ++ [nl] ++
            color_as_correct([quote(Form4)]) ++
            [nl_indent_delta(-1)],
        Spec = spec($pred, severity_error, phase_t2pt,
            get_term_context(Term), Pieces),
        MaybeCompiletime = error1([Spec])
    ).

:- pred parse_trace_runtime(varset::in, term::in,
    maybe1(trace_runtime)::out) is det.

parse_trace_runtime(VarSet, Term, MaybeRuntime) :-
    ( if
        Term = term.functor(Functor, SubTerms, TermContext),
        Functor = term.atom(Atom)
    then
        ( if Atom = "env" then
            ( if SubTerms = [SubTerm] then
                ( if
                    SubTerm = term.functor(SubFunctor, [], _),
                    ( SubFunctor = term.string(EnvVarName)
                    ; SubFunctor = term.atom(EnvVarName)
                    ),
                    EnvVarChars = string.to_char_list(EnvVarName),
                    list.filter(env_var_is_acceptable_char,
                        EnvVarChars, _, [])
                then
                    Runtime = trace_envvar(EnvVarName),
                    MaybeRuntime = ok1(Runtime)
                else
                    SubTermStr = describe_error_term(VarSet, SubTerm),
                    Pieces = [words("Error: expected an")] ++
                        color_as_correct([words("identifier")]) ++
                        [words("as the argument of the run_time parameter"),
                            quote("env"), suffix(","), words("got")] ++
                        color_as_incorrect([quote(SubTermStr),
                            suffix(".")]) ++
                        [nl],
                    Spec = spec($pred, severity_error, phase_t2pt,
                        get_term_context(SubTerm), Pieces),
                    MaybeRuntime = error1([Spec])
                )
            else
                Pieces = [words("Error: run_time parameter"),
                    quote("env"), words("takes just one argument."), nl],
                Spec = spec($pred, severity_error, phase_t2pt,
                    TermContext, Pieces),
                MaybeRuntime = error1([Spec])
            )
        else
            TermStr = describe_error_term(VarSet, Term),
            Pieces = [words("Error: expected a run_time parameter"),
                words("of the form")] ++
                color_as_correct(
                    [quote("env(""name of an environment variable"")"),
                    suffix(",")]) ++
                [words("got")] ++
                color_as_incorrect([quote(TermStr), suffix(".")]) ++
                [nl],
            Spec = spec($pred, severity_error, phase_t2pt,
                TermContext, Pieces),
            MaybeRuntime = error1([Spec])
        )
    else
        TermStr = describe_error_term(VarSet, Term),
        Pieces = [words("Error: expected a run_time parameter"),
            words("of the form")] ++
            color_as_correct(
                [quote("env(""name of an environment variable"")"),
                suffix(",")]) ++
            [words("got")] ++
            color_as_incorrect([quote(TermStr), suffix(".")]) ++
            [nl],
        Spec = spec($pred, severity_error, phase_t2pt,
            get_term_context(Term), Pieces),
        MaybeRuntime = error1([Spec])
    ).

:- pred env_var_is_acceptable_char(char::in) is semidet.

env_var_is_acceptable_char(Char) :-
    % This definition must be consistent with the check applied in
    % util/mkinit.c.
    ( char.is_alnum(Char)
    ; Char = '_'
    ).

:- pred convert_trace_params(assoc_list(trace_component, term.context)::in,
    maybe4(maybe(trace_expr(trace_compiletime)),
        maybe(trace_expr(trace_runtime)), maybe(prog_var),
        list(trace_mutable_var))::out) is det.

convert_trace_params(Components, MaybeParams) :-
    collect_trace_params(Components, no, MaybeCompileTime, no, MaybeRunTime,
        no, MaybeIO, [], MutableVars, [], Specs),
    (
        Specs = [],
        MaybeParams = ok4(MaybeCompileTime, MaybeRunTime, MaybeIO, MutableVars)
    ;
        Specs = [_ | _],
        MaybeParams = error4(Specs)
    ).

:- pred collect_trace_params(assoc_list(trace_component, term.context)::in,
    maybe(trace_expr(trace_compiletime))::in,
    maybe(trace_expr(trace_compiletime))::out,
    maybe(trace_expr(trace_runtime))::in,
    maybe(trace_expr(trace_runtime))::out,
    maybe(prog_var)::in, maybe(prog_var)::out,
    list(trace_mutable_var)::in, list(trace_mutable_var)::out,
    list(error_spec)::in, list(error_spec)::out) is det.

collect_trace_params([], !MaybeCompileTime, !MaybeRunTime, !MaybeIO,
        !MutableVars, !Specs).
collect_trace_params([Component - Context | ComponentsContexts],
        !MaybeCompileTime, !MaybeRunTime, !MaybeIO, !MutableVars, !Specs) :-
    (
        Component = trace_component_compiletime(CompileTime),
        (
            !.MaybeCompileTime = no,
            !:MaybeCompileTime = yes(CompileTime)
        ;
            !.MaybeCompileTime = yes(_),
            Pieces = [words("Error:")] ++
                color_as_incorrect(
                    [words("duplicate compile_time parameter")]) ++
                [words("in trace goal."), nl],
            Spec = spec($pred, severity_error, phase_t2pt, Context, Pieces),
            !:Specs = [Spec | !.Specs]
        )
    ;
        Component = trace_component_runtime(RunTime),
        (
            !.MaybeRunTime = no,
            !:MaybeRunTime = yes(RunTime)
        ;
            !.MaybeRunTime = yes(_),
            Pieces = [words("Error:")] ++
                color_as_incorrect([words("duplicate run_time parameter")]) ++
                [words("in trace goal."), nl],
            Spec = spec($pred, severity_error, phase_t2pt, Context, Pieces),
            !:Specs = [Spec | !.Specs]
        )
    ;
        Component = trace_component_maybe_io(IOStateVar),
        (
            !.MaybeIO = no,
            !:MaybeIO = yes(IOStateVar)
        ;
            !.MaybeIO = yes(_),
            Pieces = [words("Error:")] ++
                color_as_incorrect([words("duplicate io parameter")]) ++
                [words("in trace goal."), nl],
            Spec = spec($pred, severity_error, phase_t2pt, Context, Pieces),
            !:Specs = [Spec | !.Specs]
        )
    ;
        Component = trace_component_mutable_var(MutableVar),
        !:MutableVars = !.MutableVars ++ [MutableVar]
    ),
    collect_trace_params(ComponentsContexts, !MaybeCompileTime, !MaybeRunTime,
        !MaybeIO, !MutableVars, !Specs).

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

:- pred parse_catch_any_term(term::in, term.context::in,
    cord(format_piece)::in,
    maybe2(catch_any_expr, list(warning_spec))::out,
    prog_varset::in, prog_varset::out) is det.

parse_catch_any_term(ArrowTerm, _Context, ContextPieces, MaybeCatchAny,
        !VarSet) :-
    ( if ArrowTerm = term.functor(atom("->"), [VarTerm0, GoalTerm], _) then
        ( if VarTerm0 = term.variable(Var0, _) then
            term.coerce_var(Var0, Var),
            parse_goal(GoalTerm, ContextPieces, MaybeGoal, !VarSet),
            (
                MaybeGoal = ok2(Goal, GoalWarningSpecs),
                CatchAny = catch_any_expr(Var, Goal),
                MaybeCatchAny = ok2(CatchAny, GoalWarningSpecs)
            ;
                MaybeGoal = error2(Specs),
                MaybeCatchAny = error2(Specs)
            )
        else
            varset.coerce(!.VarSet, VarSet0),
            VarTermStr0 = describe_error_term(VarSet0, VarTerm0),
            Pieces = [words("Error: expected a")] ++
                color_as_correct([words("variable")]) ++
                [words("as the left operand of the"),
                quote("->"), words("operator inside the scope"),
                words("of a"), quote("catch_any"), words("operator, got")] ++
                color_as_incorrect([quote(VarTermStr0), suffix(".")]) ++
                [nl],
            Spec = spec($pred, severity_error, phase_t2pt,
                get_term_context(ArrowTerm), Pieces),
            MaybeCatchAny = error2([Spec])
        )
    else
        varset.coerce(!.VarSet, VarSet0),
        ArrowTermStr = describe_error_term(VarSet0, ArrowTerm),
        Pieces = [words("Error: expected an expression of the form")] ++
            color_as_correct([quote("variable -> goal")]) ++
            [words("following the "), quote("catch_any"), words("operator,"),
            words("got")] ++
            color_as_incorrect([quote(ArrowTermStr), suffix(".")]) ++
            [nl],
        Spec = spec($pred, severity_error, phase_t2pt,
            get_term_context(ArrowTerm), Pieces),
        MaybeCatchAny = error2([Spec])
    ).

:- pred parse_catch_then_try_term_args(list(term)::in,
    maybe(catch_any_expr)::in, term.context::in, cord(format_piece)::in,
    maybe2(goal, list(warning_spec))::out,
    prog_varset::in, prog_varset::out) is det.

parse_catch_then_try_term_args(CatchTermArgs, MaybeCatchAnyExpr,
        Context, ContextPieces, MaybeGoal, !VarSet) :-
    ( if CatchTermArgs = [TermA, TermB] then
        parse_sub_catch_terms(TermB, Context, ContextPieces, MaybeCatches,
             !VarSet),
        (
            MaybeCatches = ok2(Catches, CatchWarningSpecs),
            parse_else_then_try_term(TermA, Catches, MaybeCatchAnyExpr,
                Context, ContextPieces, MaybeGoal0, !VarSet),
            (
                MaybeGoal0 = ok2(Goal, GoalWarningSpecs),
                MaybeGoal = ok2(Goal, CatchWarningSpecs ++ GoalWarningSpecs)
            ;
                MaybeGoal0 = error2(Specs),
                MaybeGoal = error2(CatchWarningSpecs ++ Specs)
            )
        ;
            MaybeCatches = error2(Specs),
            MaybeGoal = error2(Specs)
        )
    else
        varset.coerce(!.VarSet, ErrorVarSet),
        ErrorTerm = term.functor(atom("catch"), CatchTermArgs, dummy_context),
        ErrorTermStr = describe_error_term(ErrorVarSet, ErrorTerm),
        Pieces = [words("Error: the"), quote("catch"), words("operator")] ++
            color_as_correct([words("should be preceded")]) ++
            [words("by a try expression of the form"),
            quote("try [try_params] main_goal then else_goal"), suffix(","),
            words("and")] ++
            color_as_correct([words("followed")]) ++
            [words("by an expression of the form"),
            quote("catch_pattern -> catch_goal"), suffix(","), words("but")] ++
            color_as_subject([quote(ErrorTermStr)]) ++
            color_as_incorrect([words("does not have this form.")]) ++
            [nl],
        Spec = spec($pred, severity_error, phase_t2pt, Context, Pieces),
        MaybeGoal = error2([Spec])
    ).

:- pred parse_sub_catch_terms(term::in, term.context::in,
    cord(format_piece)::in,
    maybe2(list(catch_expr), list(warning_spec))::out,
    prog_varset::in, prog_varset::out) is det.

parse_sub_catch_terms(Term, Context, ContextPieces, MaybeCatches, !VarSet) :-
    ( if Term = functor(atom("catch"), [CatchArrowTerm, SubTerm], _) then
        parse_catch_arrow_term(CatchArrowTerm, Context, ContextPieces,
            HeadMaybeCatch, !VarSet),
        parse_sub_catch_terms(SubTerm, Context, ContextPieces,
            TailMaybeCatches, !VarSet),
        ( if
            HeadMaybeCatch = ok2(HeadCatch, HeadWarningSpecs),
            TailMaybeCatches = ok2(TailCatches, TailWarningSpecs)
        then
            Catches = [HeadCatch | TailCatches],
            WarningSpecs = HeadWarningSpecs ++ TailWarningSpecs,
            MaybeCatches = ok2(Catches, WarningSpecs)
        else
            Specs = get_any_errors_warnings2(HeadMaybeCatch) ++
                get_any_errors_warnings2(TailMaybeCatches),
            MaybeCatches = error2(Specs)
        )
    else
        parse_catch_arrow_term(Term, Context, ContextPieces, MaybeCatch,
            !VarSet),
        (
            MaybeCatch = ok2(Catch, CatchWarningSpecs),
            MaybeCatches = ok2([Catch], CatchWarningSpecs)
        ;
            MaybeCatch = error2(Error),
            MaybeCatches = error2(Error)
        )
    ).

:- pred parse_catch_arrow_term(term::in, term.context::in,
    cord(format_piece)::in, maybe2(catch_expr, list(warning_spec))::out,
    prog_varset::in, prog_varset::out) is det.

parse_catch_arrow_term(ArrowTerm, _Context, ContextPieces, MaybeCatch,
        !VarSet) :-
    ( if ArrowTerm = term.functor(atom("->"), [PatternTerm0, GoalTerm], _) then
        term.coerce(PatternTerm0, PatternTerm),
        parse_goal(GoalTerm, ContextPieces, MaybeGoal, !VarSet),
        (
            MaybeGoal = ok2(Goal, GoalWarningSpecs),
            Catch = catch_expr(PatternTerm, Goal),
            MaybeCatch = ok2(Catch, GoalWarningSpecs)
        ;
            MaybeGoal = error2(Error),
            MaybeCatch = error2(Error)
        )
    else
        varset.coerce(!.VarSet, ErrorVarSet),
        ArrowTermStr = describe_error_term(ErrorVarSet, ArrowTerm),
        Pieces = [words("Error: expected an expression of the form")] ++
            color_as_correct([quote("catch_pattern -> catch_goal")]) ++
            [words("following the"), quote("catch"), words("operator,"),
            words("got")] ++
            color_as_incorrect([quote(ArrowTermStr), suffix(".")]) ++
            [nl],
        Spec = spec($pred, severity_error, phase_t2pt,
            get_term_context(ArrowTerm), Pieces),
        MaybeCatch = error2([Spec])
    ).

:- pred parse_else_then_try_term(term::in, list(catch_expr)::in,
    maybe(catch_any_expr)::in, term.context::in, cord(format_piece)::in,
    maybe2(goal, list(warning_spec))::out,
    prog_varset::in, prog_varset::out) is det.

parse_else_then_try_term(Term, CatchExprs, MaybeCatchAnyExpr,
        Context, ContextPieces, MaybeGoal, !VarSet) :-
    % `else' part may or may not exist in `try' goals.
    ( if Term = term.functor(term.atom("else"), [ThenTerm, ElseTerm], _) then
        parse_goal(ElseTerm, ContextPieces, MaybeElseGoal0, !VarSet),
        (
            MaybeElseGoal0 = ok2(ElseGoal, ElseWarningSpecs),
            parse_then_try_term(ThenTerm, yes(ElseGoal), CatchExprs,
                MaybeCatchAnyExpr, Context, ContextPieces, MaybeTryGoal,
                !VarSet),
            ( if
                MaybeTryGoal = error2(_),
                ThenTerm = term.functor(term.atom("then"), [_, _], ThenContext)
            then
                Pieces = [words("Error: malformed if-then-else;")] ++
                    color_as_incorrect([words("this"), quote("then"),
                        words("is missing its"), quote("if"), suffix(".")]) ++
                    [nl],
                Spec = spec($pred, severity_error, phase_t2pt,
                    ThenContext, Pieces),
                MaybeGoal = error2([Spec | ElseWarningSpecs])
            else
                MaybeGoal = MaybeTryGoal
            )
        ;
            MaybeElseGoal0 = error2(Specs),
            MaybeGoal = error2(Specs)
        )
    else
        parse_then_try_term(Term, no, CatchExprs, MaybeCatchAnyExpr,
            Context, ContextPieces, MaybeGoal, !VarSet)
    ).

:- pred parse_then_try_term(term::in, maybe(goal)::in, list(catch_expr)::in,
    maybe(catch_any_expr)::in, term.context::in, cord(format_piece)::in,
    maybe2(goal, list(warning_spec))::out,
    prog_varset::in, prog_varset::out) is det.

parse_then_try_term(ThenTryTerm, MaybeElse, CatchExprs, MaybeCatchAnyExpr,
        Context, ContextPieces, MaybeGoal, !VarSet) :-
    ( if
        ThenTryTerm = term.functor(term.atom("then"), [TryTerm, ThenTerm], _),
        TryTerm = term.functor(term.atom("try"), [ParamsTerm, TryGoalTerm],
            TryContext)
    then
        varset.coerce(!.VarSet, GenericVarSet),
        parse_try_params(GenericVarSet, Context, ParamsTerm, MaybeParams),
        parse_goal(TryGoalTerm, ContextPieces, MaybeTryGoal, !VarSet),
        parse_goal(ThenTerm, ContextPieces, MaybeThenGoal, !VarSet),
        ( if
            MaybeParams = ok1(Params),
            MaybeTryGoal = ok2(TryGoal, TryWarningSpecs),
            MaybeThenGoal = ok2(ThenGoal, ThenWarningSpecs)
        then
            WarningSpecs = TryWarningSpecs ++ ThenWarningSpecs,
            convert_try_params(Params, MaybeComponents),
            (
                MaybeComponents = ok1(MaybeIO),
                Goal = try_expr(TryContext, MaybeIO, TryGoal, ThenGoal,
                    MaybeElse, CatchExprs, MaybeCatchAnyExpr),
                MaybeGoal = ok2(Goal, WarningSpecs)
            ;
                MaybeComponents = error1(Specs),
                MaybeGoal = error2(Specs ++ WarningSpecs)
            )
        else
            Specs = get_any_errors1(MaybeParams) ++
                get_any_errors_warnings2(MaybeTryGoal) ++
                get_any_errors_warnings2(MaybeThenGoal),
            MaybeGoal = error2(Specs)
        )
    else
        Pieces = [words("Error: a")] ++
            color_as_subject([quote("try"), words("goal")]) ++
            color_as_incorrect([words("should have the form")]) ++
            [quote("try [try_params] main_goal then success_goal"),
            suffix(","),
            words("optionally followed by"),
            quote("else failure_goal"), suffix(","),
            words("which in turn may be followed by zero or more"),
            quote("catch"), words("clauses, and optionally by a single"),
            quote("catch_any"), words("clause."), nl],
        Spec = spec($pred, severity_error, phase_t2pt,
            get_term_context(ThenTryTerm), Pieces),
        MaybeGoal = error2([Spec])
    ).

:- type try_component
    --->    try_component_maybe_io(prog_var).

:- pred parse_try_params(varset::in, context::in, term::in,
    maybe1(assoc_list(try_component, term.context))::out) is det.

parse_try_params(VarSet, Context, Term, MaybeComponentsContexts) :-
    ( if Term = term.functor(term.atom("[]"), [], _) then
        MaybeComponentsContexts = ok1([])
    else if Term = term.functor(term.atom("[|]"), [HeadTerm, TailTerm], _) then
        parse_try_param(VarSet, Term, HeadTerm,
            MaybeHeadComponentContext),
        parse_try_params(VarSet, Context, TailTerm,
            MaybeTailComponentsContexts),
        ( if
            MaybeHeadComponentContext = ok1(HeadComponentContext),
            MaybeTailComponentsContexts = ok1(TailComponentsContexts)
        then
            MaybeComponentsContexts =
                ok1([HeadComponentContext | TailComponentsContexts])
        else
            Specs = get_any_errors1(MaybeHeadComponentContext) ++
                get_any_errors1(MaybeTailComponentsContexts),
            MaybeComponentsContexts = error1(Specs)
        )
    else
        TermStr = describe_error_term(VarSet, Term),
        Pieces = [words("Error: expected a")] ++
            color_as_correct([words("list of try parameters,")]) ++
            [words("got")] ++
            color_as_incorrect([quote(TermStr), suffix(".")]) ++
            [nl],
        Spec = spec($pred, severity_error, phase_t2pt,
            get_term_context(Term), Pieces),
        MaybeComponentsContexts = error1([Spec])
    ).

:- pred parse_try_param(varset::in, term::in, term::in,
    maybe1(pair(try_component, term.context))::out) is det.

parse_try_param(VarSet, _ErrorTerm, Term, MaybeComponentContext) :-
    ( if
        Term = term.functor(Functor, SubTerms, Context),
        Functor = term.atom(Atom)
    then
        ( if Atom = "io" then
            ( if SubTerms = [SubTerm] then
                ( if
                    SubTerm = term.functor(term.atom("!"),
                        [term.variable(Var, _)], _)
                then
                    term.coerce_var(Var, ProgVar),
                    Component = try_component_maybe_io(ProgVar),
                    MaybeComponentContext = ok1(Component - Context)
                else
                    SubTermStr = describe_error_term(VarSet, SubTerm),
                    Pieces = [words("Error: expected a")] ++
                        color_as_correct([words("state variable")]) ++
                        [words("as the argument of"), quote(Atom), suffix(","),
                        words("got")] ++
                        color_as_incorrect([quote(SubTermStr), suffix(".")]) ++
                        [nl],
                    Spec = spec($pred, severity_error, phase_t2pt,
                        get_term_context(SubTerm), Pieces),
                    MaybeComponentContext = error1([Spec])
                )
            else
                Pieces = [words("Error:")] ++
                    color_as_subject([fixed(Atom)]) ++
                    color_as_incorrect([words("should have one argument,")]) ++
                    [words("which should be a state variable name."), nl],
                Spec = spec($pred, severity_error, phase_t2pt,
                    Context, Pieces),
                MaybeComponentContext = error1([Spec])
            )
        else
            TermStr = describe_error_term(VarSet, Term),
            Pieces = [words("Error: expected a term of the form")] ++
                color_as_correct([quote("io(!IO)")]) ++
                [words("as try goal parameter, got")] ++
                color_as_incorrect([quote(TermStr), suffix(".")]) ++
                [nl],
            Spec = spec($pred, severity_error, phase_t2pt, Context, Pieces),
            MaybeComponentContext = error1([Spec])
        )
    else
        TermStr = describe_error_term(VarSet, Term),
        Pieces = [words("Error: expected a term of the form")] ++
            color_as_correct([quote("io(!IO)")]) ++
            [words("as try goal parameter, got")] ++
            color_as_incorrect([quote(TermStr), suffix(".")]) ++
            [nl],
        Spec = spec($pred, severity_error, phase_t2pt,
            get_term_context(Term), Pieces),
        MaybeComponentContext = error1([Spec])
    ).

:- pred convert_try_params(assoc_list(try_component, term.context)::in,
    maybe1(maybe(prog_var))::out) is det.

convert_try_params(Components, MaybeParams) :-
    collect_try_params(Components, no, MaybeIO, [], Specs),
    (
        Specs = [],
        MaybeParams = ok1(MaybeIO)
    ;
        Specs = [_ | _],
        MaybeParams = error1(Specs)
    ).

:- pred collect_try_params(assoc_list(try_component, term.context)::in,
    maybe(prog_var)::in, maybe(prog_var)::out,
    list(error_spec)::in, list(error_spec)::out) is det.

collect_try_params([], !MaybeIO, !Specs).
collect_try_params([Component - Context | ComponentsContexts],
        !MaybeIO, !Specs) :-
    Component = try_component_maybe_io(IOStateVar),
    (
        !.MaybeIO = no,
        !:MaybeIO = yes(IOStateVar)
    ;
        !.MaybeIO = yes(_),
        Pieces = [words("Error:")] ++
            color_as_incorrect([words("duplicate io parameter")]) ++
            [words("in try goal."), nl],
        Spec = spec($pred, severity_error, phase_t2pt, Context, Pieces),
        !:Specs = [Spec | !.Specs]
    ),
    collect_try_params(ComponentsContexts, !MaybeIO, !Specs).

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

:- type atomic_component
    --->    atomic_component_inner(atomic_component_state)
    ;       atomic_component_outer(atomic_component_state)
    ;       atomic_component_vars(list(prog_var)).

:- pred parse_atomic_params(context::in, term::in, varset::in,
    maybe1(assoc_list(atomic_component, term.context))::out) is det.

parse_atomic_params(Context, Term, VarSet, MaybeComponentsContexts) :-
    ( if Term = term.functor(term.atom("[]"), [], _) then
        MaybeComponentsContexts = ok1([])
    else if Term = term.functor(term.atom("[|]"), [HeadTerm, TailTerm], _) then
        parse_atomic_component(Term, HeadTerm, VarSet, MaybeHeadComponent),
        parse_atomic_params(Context, TailTerm, VarSet,
            MaybeTailComponentsContexts),
        ( if
            MaybeHeadComponent = ok1(HeadComponent),
            MaybeTailComponentsContexts = ok1(TailComponentsContexts)
        then
            MaybeComponentsContexts =
                ok1([HeadComponent | TailComponentsContexts])
        else
            Specs = get_any_errors1(MaybeHeadComponent) ++
                get_any_errors1(MaybeTailComponentsContexts),
            MaybeComponentsContexts = error1(Specs)
        )
    else
        TermStr = describe_error_term(VarSet, Term),
        Pieces = [words("Error: expected a")] ++
            color_as_correct([words("a list of atomic goal parameters,")]) ++
            [words("got")] ++
            color_as_incorrect([quote(TermStr), suffix(".")]) ++
            [nl],
        Spec = spec($pred, severity_error, phase_t2pt,
            get_term_context(Term), Pieces),
        MaybeComponentsContexts = error1([Spec])
    ).

:- pred parse_atomic_component(term::in, term::in, varset::in,
    maybe1(pair(atomic_component, term.context))::out) is det.

parse_atomic_component(ErrorTerm, Term, VarSet, MaybeComponentContext) :-
    (
        Term = term.functor(Functor, SubTerms, Context),
        ( if Functor = term.atom(Atom) then
            % XXX Make parse_atomic_subterm do the postprocessing done here.
            ( if Atom = "outer" then
                parse_atomic_subterm(VarSet, Atom, ErrorTerm, Term,
                    MaybeComponentSubTerm),
                (
                    MaybeComponentSubTerm = ok1(CompTerm),
                    Component = atomic_component_outer(CompTerm),
                    MaybeComponentContext = ok1(Component - Context)
                ;
                    MaybeComponentSubTerm = error1(Specs),
                    MaybeComponentContext = error1(Specs)
                )
            else if Atom = "inner" then
                parse_atomic_subterm(VarSet, Atom, ErrorTerm, Term,
                    MaybeComponentSubTerm),
                (
                    MaybeComponentSubTerm = ok1(CompTerm),
                    Component = atomic_component_inner(CompTerm),
                    MaybeComponentContext = ok1(Component - Context)
                ;
                    MaybeComponentSubTerm = error1(Specs),
                    MaybeComponentContext = error1(Specs)
                )
            else if Atom = "vars" then
                ( if SubTerms = [SubTerm] then
                    ContextPieces = cord.from_list([words("In"), quote("vars"),
                        words("specifier of atomic scope:")]),
                    parse_possibly_repeated_vars(SubTerm, VarSet,
                        ContextPieces, MaybeVars),
                    (
                        MaybeVars = ok1(Vars),
                        list.map(term.coerce_var, Vars, ProgVars),
                        Component = atomic_component_vars(ProgVars),
                        MaybeComponentContext = ok1(Component - Context)
                    ;
                        MaybeVars = error1(Specs),
                        MaybeComponentContext = error1(Specs)
                    )
                else
                    Pieces = [words("Error:"), words(Atom)] ++
                        color_as_incorrect(
                            [words("should have exact one argument,")]) ++
                        [words("which should be a list of variable names."),
                        nl],
                    Spec = spec($pred, severity_error, phase_t2pt,
                        Context, Pieces),
                    MaybeComponentContext = error1([Spec])
                )
            else
                Pieces = [words("Invalid atomic goal parameter."), nl],
                Spec = spec($pred, severity_error, phase_t2pt,
                    Context, Pieces),
                MaybeComponentContext = error1([Spec])
            )
        else
            TermStr = describe_error_term(VarSet, Term),
            Pieces = [words("Error: expected an")] ++
                color_as_correct([words("atomic goal parameter,")]) ++
                [words("got")] ++
                color_as_incorrect([quote(TermStr), suffix(".")]) ++
                [nl],
            Spec = spec($pred, severity_error, phase_t2pt, Context, Pieces),
            MaybeComponentContext = error1([Spec])
        )
    ;
        Term = term.variable(_, _Context),
        TermStr = describe_error_term(VarSet, Term),
        Pieces = [words("Error: expected an")] ++
            color_as_correct([words("atomic goal parameter,")]) ++
            [words("got")] ++
            color_as_incorrect([quote(TermStr), suffix(".")]) ++
            [nl],
        Spec = spec($pred, severity_error, phase_t2pt,
            get_term_context(Term), Pieces),
        MaybeComponentContext = error1([Spec])
    ).

:- pred parse_atomic_subterm(varset::in, string::in, term::in, term::in,
    maybe1(atomic_component_state)::out) is det.

parse_atomic_subterm(VarSet, Name, ErrorTerm, Term, MaybeComponentState) :-
    (
        Term = term.functor(_, SubTerms, TermContext),
        ( if
            parse_atomic_component_state_or_pair(SubTerms, ComponentState)
        then
            MaybeComponentState = ok1(ComponentState)
        else
            Pieces = [words("Error:")] ++
                color_as_subject([words(Name)]) ++
                color_as_incorrect(
                    [words("should have exactly one argument,")]) ++
                [words("which should be a state variable"),
                words("or a pair of variables."), nl],
            Spec = spec($pred, severity_error, phase_t2pt,
                TermContext, Pieces),
            MaybeComponentState = error1([Spec])
        )
    ;
        Term = term.variable(_, _TermContext),
        TermStr = describe_error_term(VarSet, Term),
        Pieces = [words("Error: expected an")] ++
            color_as_correct([words("atomic goal parameter,")]) ++
            [words("got")] ++
            color_as_incorrect([quote(TermStr), suffix(".")]) ++
            [nl],
        Spec = spec($pred, severity_error, phase_t2pt,
            get_term_context(ErrorTerm), Pieces),
        MaybeComponentState = error1([Spec])
    ).

:- pred parse_atomic_component_state_or_pair(list(term)::in,
    atomic_component_state::out) is semidet.

parse_atomic_component_state_or_pair(SubTerms, State) :-
    ( if
        SubTerms = [Term],
        Term = term.functor(term.atom("!"), [term.variable(Var, _)], _)
    then
        term.coerce_var(Var, ProgVar),
        State = atomic_state_var(ProgVar)
    else if
        SubTerms = [TermA, TermB],
        TermA = term.variable(VarA, _),
        TermB = term.variable(VarB, _)
    then
        term.coerce_var(VarA, ProgVarA),
        term.coerce_var(VarB, ProgVarB),
        State = atomic_var_pair(ProgVarA, ProgVarB)
    else
        fail
    ).

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

:- pred parse_atomic_subgoals(term::in,
    maybe3(goal, list(goal), list(warning_spec))::out,
    prog_varset::in, prog_varset::out) is det.

parse_atomic_subgoals(Term, MaybeOoMSubGoals, !VarSet) :-
    parse_atomic_subgoals_as_list(Term, MaybeSubGoals, !VarSet),
    (
        MaybeSubGoals = ok2(Goals, WarningSpecs),
        (
            Goals = [],
            Pieces = [words("Error: atomic scope")] ++
                color_as_incorrect([words("must have a goal.")]) ++
                [nl],
            Context = get_term_context(Term),
            Spec = spec($pred, severity_error, phase_t2pt, Context, Pieces),
            MaybeOoMSubGoals = error3([Spec | WarningSpecs])
        ;
            Goals = [MainSubGoal | OrElseSubGoals],
            MaybeOoMSubGoals = ok3(MainSubGoal, OrElseSubGoals, WarningSpecs)
        )
    ;
        MaybeSubGoals = error2(Specs),
        MaybeOoMSubGoals = error3(Specs)
    ).

:- pred parse_atomic_subgoals_as_list(term::in,
    maybe2(list(goal), list(warning_spec))::out,
    prog_varset::in, prog_varset::out) is det.

parse_atomic_subgoals_as_list(Term, MaybeGoals, !VarSet) :-
    ( if
        Term = term.functor(term.atom("or_else"), [LeftGoal, RightGoal], _)
    then
        parse_atomic_subgoals_as_list(LeftGoal, MaybeLeftGoalList, !VarSet),
        parse_atomic_subgoals_as_list(RightGoal, MaybeRightGoalList, !VarSet),
        ( if
            MaybeLeftGoalList = ok2(LeftGoalList, LeftWarningSpecs),
            MaybeRightGoalList = ok2(RightGoalList, RightWarningSpecs)
        then
            Goals = LeftGoalList ++ RightGoalList,
            WarningSpecs = LeftWarningSpecs ++ RightWarningSpecs,
            MaybeGoals = ok2(Goals, WarningSpecs)
        else
            Specs = get_any_errors_warnings2(MaybeLeftGoalList) ++
                get_any_errors_warnings2(MaybeRightGoalList),
            MaybeGoals = error2(Specs)
        )
    else
        % XXX Provide better ContextPieces.
        ContextPieces = cord.init,
        parse_goal(Term, ContextPieces, MaybeSubGoal, !VarSet),
        (
            MaybeSubGoal = ok2(SubGoal, WarningSpecs),
            MaybeGoals = ok2([SubGoal], WarningSpecs)
        ;
            MaybeSubGoal = error2(Specs),
            MaybeGoals = error2(Specs)
        )
    ).

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

:- pred convert_atomic_params(term::in,
    assoc_list(atomic_component, term.context)::in,
    maybe3(atomic_component_state, atomic_component_state,
        maybe(list(prog_var)))::out) is det.

convert_atomic_params(ErrorTerm, ComponentsContexts, MaybeParams) :-
    collect_atomic_params(ComponentsContexts,
        no, MaybeOuter, no, MaybeInner, no, MaybeVars, [], Specs),
    (
        Specs = [],
        Context = get_term_context(ErrorTerm),
        (
            MaybeOuter = yes(Outer),
            MaybeInner = yes(Inner),
            MaybeParams = ok3(Outer, Inner, MaybeVars)
        ;
            MaybeOuter = yes(_),
            MaybeInner = no,
            Pieces = [words("Atomic goal is missing"),
                words("a specification of the inner STM state."), nl],
            Spec = spec($pred, severity_error, phase_t2pt, Context, Pieces),
            MaybeParams = error3([Spec])
        ;
            MaybeOuter = no,
            MaybeInner = yes(_),
            Pieces = [words("Atomic goal is missing"),
                words("a specification of the outer STM state."), nl],
            Spec = spec($pred, severity_error, phase_t2pt, Context, Pieces),
            MaybeParams = error3([Spec])
        ;
            MaybeOuter = no,
            MaybeInner = no,
            Pieces = [words("Atomic goal is missing"),
                words("a specification of both"),
                words("the outer and inner STM states."), nl],
            Spec = spec($pred, severity_error, phase_t2pt, Context, Pieces),
            MaybeParams = error3([Spec])
        )
    ;
        Specs = [_ | _],
        MaybeParams = error3(Specs)
    ).

:- pred collect_atomic_params(
    assoc_list(atomic_component, term.context)::in,
    maybe(atomic_component_state)::in, maybe(atomic_component_state)::out,
    maybe(atomic_component_state)::in, maybe(atomic_component_state)::out,
    maybe(list(prog_var))::in, maybe(list(prog_var))::out,
    list(error_spec)::in, list(error_spec)::out) is det.

collect_atomic_params([], !MaybeOuter, !MaybeInner, !MaybeVars, !Specs).
collect_atomic_params([Component - CompContext | ComponentsContexts],
        !MaybeOuter, !MaybeInner, !MaybeVars, !Specs) :-
    (
        Component = atomic_component_outer(Outer),
        (
            !.MaybeOuter = no,
            !:MaybeOuter = yes(Outer)
        ;
            !.MaybeOuter = yes(_),
            % XXX We should specify the duplicate parameter.
            Pieces = [words("Error: duplicate outer atomic parameter."), nl],
            Spec = spec($pred, severity_error, phase_t2pt,
                CompContext, Pieces),
            !:Specs = !.Specs ++ [Spec]
        )
    ;
        Component = atomic_component_inner(Inner),
        (
            !.MaybeInner = no,
            !:MaybeInner = yes(Inner)
        ;
            !.MaybeInner = yes(_),
            % XXX We should specify the duplicate parameter.
            Pieces = [words("Error: duplicate inner atomic parameter."), nl],
            Spec = spec($pred, severity_error, phase_t2pt,
                CompContext, Pieces),
            !:Specs = !.Specs ++ [Spec]
        )
    ;
        Component = atomic_component_vars(Vars),
        (
            !.MaybeVars = no,
            !:MaybeVars = yes(Vars)
        ;
            !.MaybeVars = yes(_),
            % XXX We should specify the duplicate parameter.
            Pieces = [words("Error: duplicate atomic vars parameter."), nl],
            Spec = spec($pred, severity_error, phase_t2pt,
                CompContext, Pieces),
            !:Specs = !.Specs ++ [Spec]
        )
    ),
    collect_atomic_params(ComponentsContexts,
        !MaybeOuter, !MaybeInner, !MaybeVars, !Specs).

%---------------------------------------------------------------------------%
:- end_module parse_tree.parse_goal.
%---------------------------------------------------------------------------%
