:- use_module(library(yall)).

%% Part I: string given, parse tree unbound

'"a" is not a valid Prolog program' :-
  % missing end token after clause
  \+ prolog_parsetree(string("a"), _).

'"a." has a single parse tree' :-
  findall(
    PT,
    prolog_parsetree(string("a."), PT),
    PTs
  ),
  PTs = [
    prolog([clause_term([term(atom(name([name_token('a', letter_digit_token([small_letter_char(a)]))]))),end([end_token(end_char('.'))])])])
  ].

'"a. b. c." has a single parse tree' :-
  findall(
    PT,
    prolog_parsetree(string("a. b. c."), PT),
    PTs
  ),
  PTs = [
    prolog([
      clause_term([term(atom(name([name_token('a', letter_digit_token([small_letter_char(a)]))]))),
        end([end_token(end_char(.))])]),
      clause_term([
        term(atom(name([
          layout_text_sequence([layout_text(layout_char(space_char(' ')))]),
          name_token('b', letter_digit_token([small_letter_char(b)]))]))),
        end([end_token(end_char(.))])]),
      clause_term([
        term(atom(name([
          layout_text_sequence([layout_text(layout_char(space_char(' ')))]),
          name_token('c', letter_digit_token([small_letter_char(c)]))]))),
        end([end_token(end_char(.))])])])
  ].

'"a :- b." has a single parse tree' :-
  findall(
    PT,
    prolog_parsetree(string("a :- b."), PT),
    PTs
  ),
  PTs = [
    prolog([directive_term([
      term(xfx,[
        term(atom(name([
          name_token('a', letter_digit_token([small_letter_char(a)]))]))),
        op(atom(name([
          layout_text_sequence([layout_text(layout_char(space_char(' ')))]),
          name_token(':-', graphic_token([graphic_token_char(graphic_char(':')),graphic_token_char(graphic_char('-'))]))]))),
        term(atom(name([
          layout_text_sequence([layout_text(layout_char(space_char(' ')))]),
          name_token('b', letter_digit_token([small_letter_char(b)]))])))]),
      end([end_token(end_char('.'))])])
    ])
  ].

'"a :- b." cannot be parsed when ISO operators are not used'(fail) :-
  prolog_parsetree(string("a :- b."), _PT, [ targets([]) ]).

'"a b c." can be parsed with the appropriate operator' :-
  % try all variants xfx, xfy, and yfx
  maplist([Type]>>(
    prolog_parsetrees(string("a b c."), PTs, [ operators([ op(800,Type,b) ]) ]),
    length(PTs, 1)
  ), [xfx, xfy, yfx]).

'"f :- a." is valid for f not being an operator' :-
  Options = [],
  prolog_parsetrees(string("f :- a."), PTs, Options),
  length(PTs, 1).

'"f :- a." is invalid for f being an operator fy@1100'(fail) :-
  Options = [ operators([ op(1100,fy,f) ]) ],
  prolog_parsetree(string("f :- a."), _PTs, Options).

'"f :- a." is invalid for f being an operator fx@1100'(fail) :-
  Options = [ operators([ op(1100,fx,f) ]) ],
  prolog_parsetree(string("f :- a."), _PT, Options).

'"f :- a." is valid for f being an operator fy@1200' :-
  Options = [ operators([ op(1200,fy,f) ]) ],
  prolog_parsetrees(string("f :- a."), PTs, Options),
  length(PTs, 1).

'"f :- a." is not valid for f being an operator fx@1200' :-
  Options = [ operators([ op(1200,fx,f) ]) ],
  prolog_parsetrees(string("f :- a."), PTs, Options),
  length(PTs, 0).


%% Part II: parse tree given, string unbound

'Get "a." from parse tree' :-
  PT = prolog([clause_term([term(atom(name([name_token('a', letter_digit_token([small_letter_char(a)]))]))),end([end_token(end_char('.'))])])]),
  findall(
    String,
    prolog_parsetree(string(String), PT),
    Strings
  ),
  Strings = ["a."].

%% Part III: automatic deterministic conversion string->parsetree->string

%%% III.a): Example Prolog programs

prolog("a.").
prolog("a. b.").

prolog("a(b).").
prolog("a(1).").
prolog("a(1.2).").
prolog("a(X).").
prolog("a(_).").
prolog("a(1,2).").
prolog("a( 1 , 2 ).").

prolog("a(b(c)).").
prolog("a(b, c(d)).").

prolog("a(1,2,3,4,5,6,7,8,9,10).").

prolog(":- a.").
prolog(":- a, b.").
prolog(":- [load].").

prolog("a :- b.").

% Ends with layout_text_sequence

prolog("a.\n").
prolog("a. % this is a comment\n").
prolog("a.% this is a comment\n").
prolog("a. /* this is a comment */\n").

% Comments

prolog("a. % linecomment\nb.").
prolog("a.% linecomment\nb.").
prolog("a.%a%b%c\nb.").
prolog("a. /**/\nb.").
prolog("a. /* this is a comment */\nb.").
prolog("a. /*/**/\nb."). % no nesting of comments
% missing layout_char between end_token and next clause:
invalid("a./**/\nb.").
invalid("a./* this is a comment */\nb.").
% line comment ends with end_of_file
prolog("a.% linecomment").

% very long comments should not get the performance worse
prolog("/* 0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789 */\na.").
prolog("%  0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789   \na.").

% Sec. 6.3.5, Compound terms - list notation
prolog("list([]).").
prolog("list([X]).").
prolog("list([a,1]).").
prolog("list( [ b , 2 ] ).").

prolog("member(X,[X|_]).").
prolog("member(X,[_|Xs]) :- member(X,Xs).").

prolog("list([1,2,3,4,5,6,7,8,9,10]).").

% Sec. 6.3.6, Compound terms - curly bracketed term
prolog("curly({}).").
prolog("curly({a}).").
prolog("curly({a,1}).").
prolog("curly( { b , 2 } ).").

'"c({a})." is correctly parsed' :-
  prolog_parsetrees(string("c({a})."), PTs, []),
  PTs = [
    prolog([
      clause_term([
        term([
          atom(name([name_token(c,letter_digit_token([small_letter_char(c)]))])),
          open_ct(open_token(open_char('('))),
          arg_list(arg(term([
            open_curly([open_curly_token(open_curly_char('{'))]),
            term(atom(name([name_token(a,letter_digit_token([small_letter_char(a)]))]))),
            close_curly([close_curly_token(close_curly_char('}'))])
          ]))),
          close([close_token(close_char(')'))])
        ]),
        end([end_token(end_char('.'))])
      ])
    ])
  ].

% Sec. 6.3.7, Terms - double quoted list notation
prolog("a(\"\").").
prolog("a(\"s\").").
prolog("a(\"string with blanks\").").

% Back quoted text, as in SWI 7+
prolog("a(`b`).", [ back_quoted_text(yes) ]).
prolog("a(`b`).", [ targets([swi(7)]) ]).

%%% III.b): Operators

'"a(1+1)." is correctly parsed' :-
  prolog_parsetrees(string("a(1+1)."), PTs, []),
  PTs = [
    prolog([
      clause_term([
        term([
          atom(name([name_token(a,letter_digit_token([small_letter_char(a)]))])),
          open_ct(
            open_token(open_char('('))),
            arg_list(arg(term(yfx,[
              term(integer([integer_token('1',integer_constant([decimal_digit_char('1')]))])),
              op(atom(name([name_token(+,graphic_token([graphic_token_char(graphic_char(+))]))]))),
              term(integer([integer_token('1',integer_constant([decimal_digit_char('1')]))]))]))),
            close([close_token(close_char(')'))])]),
        end([end_token(end_char('.'))])
      ])
    ])
  ].

prolog("a((1+2)+3).").
prolog("a(1+(2+3)).").

% left-associative
prolog("a(1+2+3).", [ targets([]), operators([ op(500,xfy,+) ]) ]).
prolog("a(1+2+3+4).", [ targets([]), operators([ op(500,xfy,+) ]) ]).

% right-associative
prolog("a(2+4+6).", [ targets([]), operators([ op(500,yfx,+) ]) ]).

% mixed
prolog("p(1 a 2 b 3 c 4).", [ operators([ op(200, xfx, b), op(300, xfx, a), op(400, xfx, c) ]) ]).

prolog("a b c.", [ operators([ op(600, xfx, b) ]) ]).
prolog("a b c.", [ operators([ op(600, yfx, b) ]) ]).
prolog("a b c.", [ operators([ op(600, xfy, b) ]) ]).

% Sec. 6.3, Table 5, valid terms
prolog("fx (fx 1).", [ operators([ op(600, fx, fx) ]) ]).
prolog("(1 xf) xf.", [ operators([ op(600, xf, xf) ]) ]).
prolog("(1 xfx 2) xfx 3.", [ operators([ op(600, xfx, xfx) ]) ]).
prolog("1 xfx (2 xfx 3).", [ operators([ op(600, xfx, xfx) ]) ]).

% Sec. 6.3, Table 5, invalid terms
invalid("fx fx 1.", [ operators([ op(600, fx, fx) ]) ]).
invalid("1 xf xf.", [ operators([ op(600, xf, xf) ]) ]).
invalid("1 xfx 2 xfx 3.", [ operators([ op(600, xfx, xfx) ]) ]).

% Operators have precedence 1201 and need to be bracketed (Sec. 6.3.1.3)
prolog( "a :- b, f.").
invalid("a :- c, f.",  [ operators([ op(600, fx, f) ]) ]).
prolog( "a :- d, (f).", [ operators([ op(600, fx, f) ]) ]).
prolog( "a :- e, f.",   [ operators([ op(600, fx, f) ]), allow_operator_as_operand(yes) ]).

% Others
prolog("a(fx b).", [ operators([ op(100, fx, fx) ]) ]).
prolog("a(fx(b)).", [ operators([ op(100, fx, fx) ]) ]).

% Corr. 2, bar resp. ht_sep as operator
prolog("a | b.", [ operators([ op(1050, xfy, '|') ]) ]).


%%% III.c): Complex Example Prolog programs

prolog("a :- b, d.", [ targets([swi]) ]).
prolog("a :- b:c.", [ targets([swi]) ]).
prolog("a :- b:c, d.", [ targets([swi]) ]).

prolog("a :- fx q, c.", [ operators([ op(600, fx, fx) ]) ]).
prolog("a :- fy q, c.", [ operators([ op(600, fy, fy) ]) ]).
prolog("a :- q xf, c.", [ operators([ op(600, xf, xf) ]) ]).
prolog("a :- q yf, c.", [ operators([ op(600, yf, yf) ]) ]).

%% Part IV: handling of options

'Explicit statement of pre-defined ISO operators is allowed' :-
  Options = [
    operators([ op(1200, xfx, ':-') ]),
    targets([iso])
  ],
  prolog_parsetree(string("a."), _, Options), !.

'"A :- B." is valid for var_prefix(yes)' :-
  Options = [
    var_prefix(yes)
  ],
  prolog_parsetrees(string("A :- B."), PTs, Options),
  !,
  PTs = [ _SingleResult ].

'"A B." is valid for var_prefix(yes) and A as prefix operator' :-
  Options = [
    var_prefix(yes),
    operators([ op(300, fx, 'A') ])
  ],
  prolog_parsetrees(string("A B."), PTs, Options),
  !,
  PTs = [ _SingleResult ].

'"F(1)." is invalid for allow_variable_name_as_functor(no)' :-
  Options = [
    allow_variable_name_as_functor(no)
  ],
  prolog_parsetrees(string("F(1)."), PTs, Options),
  !,
  PTs = [].

'"F(1)." is valid for allow_variable_name_as_functor(yes)' :-
  Options = [
    allow_variable_name_as_functor(yes)
  ],
  prolog_parsetrees(string("F(1)."), PTs, Options),
  !,
  PTs = [ _SingleResult ].

'"_x(1)." is valid for allow_variable_name_as_functor(yes)' :-
  Options = [
    allow_variable_name_as_functor(yes)
  ],
  prolog_parsetrees(string("_x(1)."), PTs, Options),
  !,
  PTs = [ _SingleResult ].

'"_(1)." is valid for allow_variable_name_as_functor(yes)' :-
  Options = [
    allow_variable_name_as_functor(yes)
  ],
  prolog_parsetrees(string("_(1)."), PTs, Options),
  !,
  PTs = [ _SingleResult ].

invalid("A(B(C)).", [allow_variable_name_as_functor(no)] ).
prolog("A(B(C)).", [allow_variable_name_as_functor(yes)] ).

invalid("a(b :- c).", [ allow_arg_precedence_geq_1000(no) ]).
prolog( "a(b :- c).", [ allow_arg_precedence_geq_1000(yes) ]).

'"a(b :- c, d)." has single parse tree for allow_arg_precedence_geq_1000(yes)' :-
  Options = [ allow_arg_precedence_geq_1000(yes) ],
  prolog_parsetrees(string("a(b :- c, d)."), PTs, Options),
  !,
  PTs = [ _SingleResult ].

/* Correct handling of end_token */

invalid("a.x."). % for '.' not an infix operator
prolog( "a.y.", [ operators([ op(100,xfy,'.') ]) ]).

'"a. b." has a single parse tree, even for \'.\' as infix operator' :-
  Options = [
    operators([ op(100,yfx,'.') ])
  ],
  prolog_parsetrees(string("a. b."), PTs, Options),
  !,
  PTs = [ _SingleResult ].


/* The following is valid according to ISO and reads as:
a("b\
").
*/
prolog( "a(\"b\\\n\").", [ allow_newline_as_quote_char(no) ]).

invalid("a(\"b\n\").", [ allow_newline_as_quote_char(no) ]).
prolog( "a(\"b\n\").", [ allow_newline_as_quote_char(yes) ]).

invalid("a(\"b\t\").", [ allow_tab_as_quote_char(no) ]).
prolog( "a(\"b\t\").", [ allow_tab_as_quote_char(yes) ]).

% Allow special escape chars for SWI-Prolog
invalid("a(\"b\\c\\n\").", [ allow_symbolic_no_output_char_c(no) ]).
prolog( "a(\"b\\c\\n\").", [ allow_symbolic_no_output_char_c(yes) ]).

invalid("a(\"b\\e\\n\").", [ allow_symbolic_escape_char_e(no) ]).
prolog( "a(\"b\\e\\n\").", [ allow_symbolic_escape_char_e(yes) ]).

invalid("a(\"b\\s\\n\").", [ allow_symbolic_space_char_s(no) ]).
prolog( "a(\"b\\s\\n\").", [ allow_symbolic_space_char_s(yes) ]).

% ISO has only restricted charset
invalid("% é Ä § ° \na.").
prolog( "% é Ä § ° \na.", [ allow_unicode(yes) ]).

invalid("f('é').").
prolog( "f('é').", [ allow_unicode(yes) ]).

invalid("é.").
prolog( "é.", [ allow_unicode(yes) ]).

invalid("a :- Ä = 1.").
prolog( "a :- Ä = 1.", [ allow_unicode(yes) ]).

%%% IV.b): Dicts

invalid("a(_{}).").
invalid("a(A{}).").
invalid("a(b{}).").
prolog( "a(_{}).", [ dicts(yes) ]).
prolog( "a(A{}).", [ dicts(yes) ]).
prolog( "a(b{}).", [ dicts(yes) ]).

% tag is either a variable or atom
invalid("a(1{}).", [ dicts(yes) ]).

% key is either an atom or small integer
prolog( "a(_{ b: 1 }).", [ dicts(yes) ]).
prolog( "a(_{ b: 1, c: 2 }).", [ dicts(yes) ]).
prolog( "a(_{ 1: b }).", [ dicts(yes) ]).
invalid( "a(_{ 1.1: b }).", [ dicts(yes) ]).
invalid( "a(_{ X: b }).", [ dicts(yes) ]).

% dot operator
prolog( "p(X) :- X = point{x:1,y:2}.x.", [ dicts(yes), operators([op(100,yfx,'.')]) ]).

%%% IV.c): digit groups (http://www.swi-prolog.org/pldoc/man?section=syntax#sec:2.16.1.5)

invalid("a(1_000).").
invalid("a(1 000).").

prolog( "a(0_000).", [ allow_digit_groups_with_underscore(yes) ]).
prolog( "a(2_000).", [ allow_digit_groups_with_underscore(yes) ]).
prolog( "a(2_0_0_0).", [ allow_digit_groups_with_underscore(yes) ]).
prolog( "a(2 000).", [ allow_digit_groups_with_space(yes) ]).
prolog( "a(2 0 0 0).", [ allow_digit_groups_with_space(yes) ]).
prolog( "a(2_0 0).", [ allow_digit_groups_with_space(yes), allow_digit_groups_with_underscore(yes) ]).

% also for different radix
invalid("a(0b1000_0000).").
prolog( "a(0b1000_0000).", [ allow_digit_groups_with_underscore(yes) ]).
invalid("a(0b1000 0000).").
prolog( "a(0b1000 0000).", [ allow_digit_groups_with_space(yes) ]).

invalid("a(0o7654_3210).").
prolog( "a(0o7654_3210).", [ allow_digit_groups_with_underscore(yes) ]).
invalid("a(0o7654 3210).").
prolog( "a(0o7654 3210).", [ allow_digit_groups_with_space(yes) ]).

invalid("a(0xffff_FFFF).").
prolog( "a(0xffff_FFFF).", [ allow_digit_groups_with_underscore(yes) ]).
invalid("a(0xffff FFFF).").
prolog( "a(0xffff FFFF).", [ allow_digit_groups_with_space(yes) ]).

% only allowed for integer_constants
invalid("a(4.0e3_0).", [ allow_digit_groups_with_underscore(yes) ]).

% bracketed comment allows after underscore
prolog( "a(3_/*some*/000).", [ allow_digit_groups_with_underscore(yes) ]).
invalid("a(3_/*some*/_000).", [ allow_digit_groups_with_underscore(yes) ]).

% allow_integer_exponential_notation
prolog( "a(1.0e3).").
prolog( "a(1.0E3).").
invalid("a(1e3).").
invalid("a(1E3).").

prolog( "b(1e3).", [ allow_integer_exponential_notation(yes) ]).
prolog( "b(1E3).", [ allow_integer_exponential_notation(yes) ]).

% closing backslash in character escapes
prolog( "a(\'\\7\\\')."). % closing backslash being present
invalid("a(\'\\7\').").
prolog( "a(\'\\7\').", [ allow_missing_closing_backslash_in_character_escape(yes) ]).

prolog( "a(\'\\x7\\\')."). % closing backslash being present
invalid("a(\'\\x7\').").
prolog( "a(\'\\x7\').", [ allow_missing_closing_backslash_in_character_escape(yes) ]).

% unicode character escapes
invalid("a('\\u1234').").
prolog( "a('\\u1234').", [ allow_unicode_character_escape(yes) ]).
invalid("a('\\U12345678').").
prolog( "a('\\U12345678').", [ allow_unicode_character_escape(yes) ]).


%%% IV.d) Others

invalid("a(0'').").
prolog( "a(0'').", [ allow_single_quote_char_in_character_code_constant(yes) ]).

invalid("a().").
prolog( "a().", [ allow_compounds_with_zero_arguments(yes) ]).
invalid("a(pi()).").
prolog( "a(pi()).", [ allow_compounds_with_zero_arguments(yes) ]).

invalid("#! test\na.").
prolog( "#! test\na.", [ allow_shebang(yes) ]).
invalid("\n#! test\na.", [ allow_shebang(yes) ]).
invalid(" #! test2\na.", [ allow_shebang(yes) ]).


%% Part V: infer operator definitions

'"a b." invalid for infer_operators(no)' :-
  Options = [ targets([]), infer_operators(no) ],
  \+ prolog_parsetree(string("a b."), _, Options).

'"a b." valid for infer_operators(yes)' :-
  Options = [ targets([]), infer_operators(yes) ],
  prolog_parsetrees(string("a b."), PTs, Options),
  length(PTs, PTs_Count),
  % with the current implementation, it is 4, because of:
  % a@fx, b not an operator
  % a@fy, b not an operator
  % b@xf, a not an operator
  % b@yf, a not an operator
  PTs_Count = 4.

'"a b." valid for infer_operators(Ops)' :-
  findall(
    Ops,
    prolog_parsetree(
      string("a b."),
      _PT,
      [ targets([]), infer_operators(Ops) ]
    ),
    Inferred_Ops
  ),
  Expected = [
    [op(_,yf,b)],
    [op(_,xf,b)],
    [op(_,fy,a)],
    [op(_,fx,a)]
  ],
  permutation2(Inferred_Ops, Expected), !.

%% Part VI: use operator definitions given in source

prolog(":- op(600,fx,p).\np q.").
prolog(":- module(a,[op(600,fx,p)]).\np q.").
prolog(":- module(a,[b,op(600,fx,p),c]).\np q.").
prolog(":- module(a,[b,op(600,fx,p),c/2]).\np q.").

%% Part VII: Various test cases (mainly because of observed bugs)

'"a(+)." has a single parse tree for target "swi"' :-
  Options = [ targets([swi]) ],
  prolog_parsetrees(string("a(+)."), PTs, Options),
  !,
  PTs = [ _SingleResult ].

prolog("a(1.0e1).").
prolog("a(1.0e+2).").
prolog("a(1.0e-3).").
prolog("a(1e+4).", [ allow_integer_exponential_notation(yes) ]).
prolog("a(1e-4).", [ allow_integer_exponential_notation(yes) ]).

% a(0'\s)
prolog("a(0'\\s).", [ allow_symbolic_space_char_s(yes) ]).
