Files
mercury/tests/hard_coded/flatten_disjunctions.m
Zoltan Somogyi b96a90f948 Parse disjunctions using tail recursive code ...
to allow even very long disjunctions to be parsed in constant stack space.
This fixes Mantis bug #559.

compiler/prog_item.m:
    We used to represent a disjunction like ( GoalA ; GoalB ; GoalC ; GoalD )
    in the parse tree as

        disj_expr(ContextA, GoalA,
            disj_expr(ContextB, GoalB,
                disj_expr(ContextC, GoalC,
                    GoalD)))

    To enable the changes in parse_goal.m and parse_dcg_goal.m, switch over
    to representing them as

        disj_expr(ContextA, GoalA, GoalB, [GoalC, GoalD])

    The type of this term enforces the invariant that a disjunction
    must have at least two disjuncts.

    The fact that this throws away ContextB and ContextC is not a problem;
    they were never used, being thrown away when converting the parse tree
    to the HLDS.

compiler/parse_goal.m:
compiler/parse_dcg_goal.m:
    After seeing the first comma in the above disjunction, these parsers
    used to (1) parse its left operand, GoalA, (2) parse its right operand,
    ( GoalB ; GoalC ; GoalD), and then (3) check for errors. This code
    was prevented from being tail recursive both by the presence of step 3,
    and the fact that step 2 indirectly invokes another predicate that
    (until my previous change to parse_goal.m) had a different determinism.

    Fix the first issue by having the new predicate parse_goal_disjunction,
    and its DCG variant, accumulate errors *alongside* disjuncts,
    to be checked just once, at the end, outside the loop. Fix the second
    issue by having parse_goal_disjunction test whether the right operand
    of the semicolon has the form of a disjunction, and if it does,
    recursing on it directly. This makes parse_goal_disjunction
    self-tail-recursive, which should allow it to process disjunctions
    of arbtrary length using fixed stack space (in grades that support
    tail recursion, that is).

    Move the code to flatten disjunctions from goal_expr_to_goal.m to
    these modules, because it is simpler to get the right result this way
    in DCG clauses (for non-DCG clauses, it works simply either way).

compiler/goal_expr_to_goal.m:
    Convert the updated parse tree representation of disjunctions to HLDS,
    and don't flatten disjunctions here anymore.

compiler/parse_item.m:
    Add some infrastructure for debugging changes like this.

compiler/add_clause.m:
    Improve the infrastructure of debugging changes like this, by making it
    more selective.

    To make this possible, pass the predicate name to a predicate
    that did not need it before. Fix the argument order of that predicate.

compiler/make_hlds_warn.m:
    Don't flatten parse tree disjunctions, since the code constructing them
    has done it already.

compiler/get_dependencies.m:
compiler/module_qual.collect_mq_info.m:
compiler/parse_tree_out_clause.m:
compiler/prog_item_stats.m:
compiler/prog_util.m:
    Conform to the change in prog_item.m.

compiler/instance_method_clauses.m:
    Conform to the change in add_clause.m.

tests/hard_coded/flatten_disjunctions.{m,exp}:
    A new test case both testing and documenting the need for flattening
    disjunctions.

tests/hard_coded/Mmakefile:
    Enable the new test case.

tests/invalid/require_switch_arms_detism.err_exp:
    Expect updated numbers for anonymous variables. This is due to
    goal_expr_to_goal.m now processing disjuncts in a different order
    than before.
2022-05-06 15:19:49 +10:00

132 lines
3.8 KiB
Mathematica

%---------------------------------------------------------------------------%
% vim: ft=mercury ts=4 sw=4 et
%---------------------------------------------------------------------------%
%
% This test case tests whether the parser flattens nested disjunctions,
% both in normal clauses (predicate p), and in DCG clauses (predicate dcg_p).
%
% The main bodies of those predicates are disjunctions that are effectively
% switches on the value of A. Switch detection looks for unifications
% that could allow it to turn a disjunction into a switch in disjuncts
% of that disjunction, and in disjuncts inside those disjuncts; it does NOT
% look for them in disjuncts inside disjuncts inside disjuncts. In other
% words, it looks at a unifications at a maximum depth of two levels.
%
% In the written form of these predicates, the unifications in the innermost
% disjunction "( A = 4 ; A = 5 )" are at a depth of three. Switch detection
% can nevertheless turn both predicate bodies into switches, because parsing
% has traditionally flattened disjunctions, which means that if a disjunct
% consists entirely of another disjunction, then it replaced that outer
% disjunct with the arms of the inner disjunction. In this case, this
% flattening brings the A = 4 and A = 5 unifications that used to be
% at depth three to depth two, where switch detection can see them.
%
% This test case tests that the parsers (parse_goal.m and parse_dcg_goal.m)
% do flatten disjunctions. If they don't, then switch detection will leave
% at least one disjunction in p and/or dcg_p, and the compilation of the
% affected predicate(s) will fail with a determinism error.
%
% The original code from which this test code is distilled is the
% read_parse_tree_src_components predicate in compiler/parse_module.m,
% which (as of 2022 may 5) has a switch on IOM that switch detection
% recognizes *only* if disjunctions have been flattened by then.
%
%---------------------------------------------------------------------------%
:- module flatten_disjunctions.
:- interface.
:- import_module io.
:- pred main(io::di, io::uo) is det.
:- implementation.
:- import_module int.
:- import_module list.
:- import_module string.
main(!IO) :-
( if p(6, B, 4, X) then
io.format("p(6, %d, 4, %d) succeeded.\n", [i(B), i(X)], !IO)
else
io.format("p(6, _, 4, _) failed.\n", [], !IO)
),
( if dcg_p(6, DCG_B, 4, DCG_X) then
io.format("dcg_p(6, %d, 4, %d) succeeded.\n",
[i(DCG_B), i(DCG_X)], !IO)
else
io.format("dcg_p(6, _, 4, _) failed.\n", [], !IO)
).
:- pred p(int::in, int::out, int::in, int::out) is semidet.
:- pragma no_inline(pred(p/4)).
p(A, B, !X) :-
(
A = 1, B = 11
;
( A = 4
; A = 5
; A = 6
; A = 7
),
(
(
( A = 4
; A = 5
)
;
A = 6,
!:X = !.X + 6
),
(
!.X = 10,
B = 5
;
!.X = 11,
B = 6
)
;
A = 7,
B = A
)
).
:- pred dcg_p(int::in, int::out, int::in, int::out) is semidet.
:- pragma no_inline(pred(dcg_p/4)).
dcg_p(A, B) -->
(
{ A = 1, B = 11 }
;
( { A = 4 }
; { A = 5 }
; { A = 6 }
; { A = 7 }
),
(
(
( { A = 4 }
; { A = 5 }
)
;
{ A = 6 },
=(X0),
:=(X0 + 6)
),
(
=(X1),
{ X1 = 10 },
{ B = 5 }
;
=(X2),
{ X2 = 11 },
{ B = 6 }
)
;
{ A = 7 },
{ B = A }
)
).