:- op(800, xfx, <=>). % succeeding tests
:- op(800, xfx, <#>). % failing tests

heads(Symbol, DCGBody, In, Head1, Head2) :-
  In = [First|_], integer(First), !,
  format(atom(Head1), '~w ~w> "~s"', [DCGBody, Symbol, In]),
  format(atom(Head2), '~w ~w< "~s"', [DCGBody, Symbol, In]).
heads(Symbol, DCGBody, In, Head1, Head2) :-
  format(atom(Head1), '~w ~w> ~w', [DCGBody, Symbol, In]),
  format(atom(Head2), '~w ~w< ~w', [DCGBody, Symbol, In]).

% Define term expansion for TAP tests

%% succeeding tests, using `<=>`
term_expansion(DCGBody: PT <=> In, [(Head1 :- Test1), (Head2 :- Test2)]) :-
  heads('', DCGBody, In, Head1, Head2),
  DCGBody =.. DCGBody_L,
  % build first test: from input to parsetree
  append(DCGBody_L, [PT1], DCGBody_L1),
  DCGBody1 =.. DCGBody_L1,
  Test1 = (
    phrase(DCGBody1, In, []), !,
    PT1 = PT
  ),
  % build second test: from parsetree to input
  append(DCGBody_L, [PT], DCGBody_L2),
  DCGBody2 =.. DCGBody_L2,
  Test2 = (
    phrase(DCGBody2, In2, []), !,
    In2 = In
  ),
  % register tests
  tap:register_test(Head1),
  tap:register_test(Head2).

%% failing tests, using `<#>`
term_expansion(DCGBody: PT <#> In, [(Head1 :- Test1), (Head2 :- Test2)]) :-
  heads('!', DCGBody, In, Head1, Head2),
  DCGBody =.. DCGBody_L,
  % build first test: from input to parsetree
  append(DCGBody_L, [_PT1], DCGBody_L1),
  DCGBody1 =.. DCGBody_L1,
  Test1 = (
    \+ phrase(DCGBody1, In, [])
  ),
  % build second test: from parsetree to input
  append(DCGBody_L, [PT], DCGBody_L2),
  DCGBody2 =.. DCGBody_L2,
  Test2 = (
    \+ phrase(DCGBody2, _In2, [])
  ),
  % register tests
  tap:register_test(Head1),
  tap:register_test(Head2).

% Message hook to not print warnings
message_hook(_,warning,_) :-
  true. % just ignore

% Use term expansion of DCG4PT
:- use_module(library(dcg4pt/expand)).

% DCGs to be tested starting here.

%% Test Section I: DCGs

c1 --> "a".
c2 --> "abc".

c(1) --> "a".
c(2) --> "abc".

nonterminal --> c1.
nonterminal(1) --> c1.
nonterminal(2) --> c2.

d_or_e --> "d".
d_or_e --> "e".

or1 --> "d" | "e".

%% Test Section II: DCGs

sentence --> noun_phrase, verb_phrase.
noun_phrase --> determiner, noun.
verb_phrase --> ( verb ; verb, noun_phrase ).
noun --> [boy] ; [boys] ; [apple] ; [apples].
determiner --> [the].
verb --> [eat] ; [eats].

single --> [some].

with_argument(1) --> [some].

sequence_star --> sequence('*', single).
sequence_star2 --> sequence('**', single).
sequence_plus --> sequence('+', single).
sequence_question --> sequence('?', single).

sequences --> [other], sequence('*', single).
sequences2 --> [other], sequence('**', single).

:- op(800, fy, *).
:- op(800, fy, **).
*(A,B,C,D) :- sequence('*', A, B, C, D).
**(A,B,C,D) :- sequence('**', A, B, C, D).

sequence_star_prefix --> *single.
sequence_star2_prefix --> **single.

% Example calls to be tested starting here.

:- use_module(library(tap)).

%% Test Section I: Tests

c1: c1("a") <=> `a`.
c2: c2("abc") <=> `abc`.

c(1): c("a") <=> `a`.
c(2): c("abc") <=> `abc`.

d_or_e: d_or_e("d") <=> `d`.
d_or_e: d_or_e("e") <=> `e`.

or1: or1("d") <=> `d`.
or1: or1("e") <=> `e`.

nonterminal: nonterminal(c1("a")) <=> `a`.
nonterminal(1): nonterminal(c1("a")) <=> `a`.
nonterminal(2): nonterminal(c2("abc")) <=> `abc`.

% Test Section II: Tests

single: single(some) <=> [some].
with_argument(1): with_argument(some) <=> [some].
verb: verb(eat) <=> [eat].
verb_phrase: verb_phrase(verb(eat)) <=> [eat].
sequence_star: sequence_star([]) <=> [].
sequence_star: sequence_star([single(some)]) <=> [some].
sequence_star: sequence_star([single(some), single(some)]) <=> [some,some].
sequence_star2: sequence_star2([]) <=> [].
sequence_star2: sequence_star2([single(some)]) <=> [some].
sequence_star2: sequence_star2([single(some), single(some)]) <=> [some,some].
sequence_plus: sequence_plus([single(some)]) <=> [some].
sequence_plus: sequence_plus([single(some), single(some)]) <=> [some,some].
sequence_star_prefix: sequence_star_prefix([single(some), single(some)]) <=> [some,some].
sequence_star2_prefix: sequence_star2_prefix([single(some), single(some)]) <=> [some,some].
sequence_question: sequence_question([]) <=> [].
sequence_question: sequence_question([single(some)]) <=> [some].
sequences: sequences([other, single(some)]) <=> [other,some].
sentence:
  sentence([
    noun_phrase([
      determiner(the),
      noun(boy)
    ]),
    verb_phrase([
      verb(eats),
      noun_phrase([
        determiner(the),
        noun(apple)
      ])
    ])
  ]) <=> [the, boy, eats, the, apple].

sequence_plus: sequence_plus([]) <#> [].
sequence_question:
  sequence_question([single(some), single(some)]) <#> [some,some].

% when both unbound, generate results and leave choice points
'sequences work with unbound arguments (Result 1)' :-
  phrase(sequences(PT), In),
  PT = sequences([other]), In = [other], !.
'sequences work with unbound arguments (Result 2)' :-
  phrase(sequences(PT), In),
  PT = sequences([other, single(some)]), In = [other, some], !.
'sequences work with unbound arguments (Result 3)' :-
  phrase(sequences(PT), In),
  PT = sequences([other, single(some), single(some)]), In = [other, some, some], !.
