%%
%% %CopyrightBegin%
%%
%% Copyright Ericsson AB 2005-2010. All Rights Reserved.
%%
%% The contents of this file are subject to the Erlang Public License,
%% Version 1.1, (the "License"); you may not use this file except in
%% compliance with the License. You should have received a copy of the
%% Erlang Public License along with this software. If not, it can be
%% retrieved online at http://www.erlang.org/.
%%
%% Software distributed under the License is distributed on an "AS IS"
%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
%% the License for the specific language governing rights and limitations
%% under the License.
%%
%% %CopyrightEnd%
%%
-module(yecc_SUITE).

%-define(debug, true).

-include_lib("stdlib/include/erl_compile.hrl").

-ifdef(debug).
-define(line, put(line, ?LINE), ).
-define(config(X,Y), foo).
-define(datadir, "yecc_SUITE_data").
-define(privdir, "yecc_SUITE_priv").
-define(t, test_server).
-else.
-include("test_server.hrl").
-define(datadir, ?config(data_dir, Config)).
-define(privdir, ?config(priv_dir, Config)).
-endif.

-export([all/1, init_per_testcase/2, fin_per_testcase/2]).

-export([app_test/1,
         checks/1, 
             file/1, syntax/1, compile/1, rules/1, expect/1,
             conflicts/1,
         examples/1,
             empty/1, prec/1, yeccpre/1, lalr/1, old_yecc/1, 
             other_examples/1,
         bugs/1, 
             otp_5369/1, otp_6362/1, otp_7945/1, otp_8483/1, otp_8486/1,
         improvements/1,
             otp_7292/1, otp_7969/1]).

% Default timetrap timeout (set in init_per_testcase).
-define(default_timeout, ?t:minutes(1)).

init_per_testcase(_Case, Config) ->
    ?line Dog = ?t:timetrap(?default_timeout),
    [{watchdog, Dog} | Config].

fin_per_testcase(_Case, Config) ->
    Dog = ?config(watchdog, Config),
    test_server:timetrap_cancel(Dog),
    ok.

all(suite) -> [app_test, checks, examples, bugs, improvements].

app_test(doc) ->
    ["Tests the applications consistency."];
app_test(suite) ->
    [];
app_test(Config) when is_list(Config) ->
    ?line ok=?t:app_test(parsetools),
    ok.

checks(suite) ->
    [file, syntax, compile, rules, expect, conflicts].

file(doc) ->
    "Bad files and options.";
file(suite) -> [];
file(Config) when is_list(Config) ->
    Dir = ?privdir,
    Ret = [return, {report, false}],
    ?line {error,[{_,[{none,yecc,{file_error,_}}]}],[]} = 
        yecc:file("not_a_file", Ret),
    ?line {error,[{_,[{none,yecc,{file_error,_}}]}],[]} = 
        yecc:file("not_a_file", [{return,true}]),
    ?line {error,[{_,[{none,yecc,{file_error,_}}]}],[]} = 
        yecc:file("not_a_file", [{report,false},return_errors]),
    ?line error = yecc:file("not_a_file"),
    ?line error = yecc:file("not_a_file", [{return,false},report]),
    ?line error = yecc:file("not_a_file", [return_warnings,{report,false}]),
    Filename = filename:join(Dir, "file.yrl"),
    file:delete(Filename),

    ?line {'EXIT', {badarg, _}} = (catch yecc:file({foo})),
    ?line {'EXIT', {badarg, _}} = 
        (catch yecc:file(Filename, {parserfile,{foo}})),
    ?line {'EXIT', {badarg, _}} = 
        (catch yecc:file(Filename, {includefile,{foo}})),

    ?line {'EXIT', {badarg, _}} = (catch yecc:file(Filename, no_option)),
    ?line {'EXIT', {badarg, _}} = 
        (catch yecc:file(Filename, [return | report])),
    ?line {'EXIT', {badarg, _}} = 
        (catch yecc:file(Filename, {return,foo})),
    ?line {'EXIT', {badarg, _}} = 
        (catch yecc:file(Filename, includefile)),

    Mini = <<"Nonterminals nt. 
              Terminals t.
              Rootsymbol nt.
              nt -> t.">>,
    ?line ok = file:write_file(Filename, Mini),
    ?line {error,[{_,[{none,yecc,{file_error,_}}]}],[]} = 
        yecc:file(Filename, [{parserfile,"//"} | Ret]),

    ?line {error,[{_,[{none,yecc,{file_error,_}}]}],[]} = 
        yecc:file(Filename, [{includefile,"//"} | Ret]),
    ?line {error,[{_,[{none,yecc,{file_error,_}}]}],[]} = 
        yecc:file(Filename, [{includefile,"/ /"} | Ret]),

    YeccPre = filename:join(Dir, "yeccpre.hrl"),
    ?line ok = file:write_file(YeccPre, <<"syntax error. ">>),
    PreErrors1 = run_test(Config, Mini, YeccPre),
    ?line {errors,[_],[]} = extract(YeccPre, PreErrors1),
    ?line ok = file:write_file(YeccPre, my_yeccpre()),
    ?line {'EXIT', {undef,_}} = (catch run_test(Config, Mini, YeccPre)),

    MiniCode = <<"
              Nonterminals nt. 
              Terminals t.
              Rootsymbol nt.
              nt -> t.
              Erlang code.
             ">>,
    ?line {'EXIT', {undef,_}} = (catch run_test(Config, MiniCode, YeccPre)),

    file:delete(YeccPre),
    file:delete(Filename),

    ok.

syntax(doc) ->
    "Syntax checks.";
syntax(suite) -> [];
syntax(Config) when is_list(Config) ->
    Dir = ?privdir,
    %% Report errors. Very simple test of format_error/1.
    Ret = [return, {report, true}],
    Filename = filename:join(Dir, "file.yrl"),
    Parserfile1 = filename:join(Dir, "a file"),

    ?line ok = file:write_file(Filename, <<"">>),
    ?line {error,[{_,[{none,yecc,no_grammar_rules},
                      {none,yecc,nonterminals_missing},
                      {none,yecc,rootsymbol_missing},
                      {none,yecc,terminals_missing}]}],[]} = 
        yecc:file(Filename, Ret),

    ?line ok = file:write_file(Filename, <<"Nonterminals">>),
    ?line {error,[{_,[{_,yecc,{error,yeccparser,_}}]}],[]} = 
        yecc:file(Filename, Ret),

    ?line ok = file:write_file(Filename, <<"Nonterminals nt.">>),
    ?line {error,[{_,[{none,yecc,no_grammar_rules},
                      {none,yecc,rootsymbol_missing},
                      {none,yecc,terminals_missing}]}],[]} = 
        yecc:file(Filename, Ret),

    ?line ok = file:write_file(Filename, <<"Nonterminals nt. Terminals t.">>),
    ?line {error,[{_,[{none,yecc,no_grammar_rules},
                      {none,yecc,rootsymbol_missing}]}],[]} = 
        yecc:file(Filename, Ret),

    %% Nonterminals and terminals not disjoint.
    ?line ok = file:write_file(Filename, 
         <<"Nonterminals nt 't t'. Terminals t 't t'. Rootsymbol nt.">>),
    ?line {error,[{_,[{1,yecc,{symbol_terminal_and_nonterminal,'t t'}},
                      {none,yecc,no_grammar_rules}]}],
           []} = yecc:file(Filename, Ret),

    %% Rootsymbol is not a nonterminal.
    ?line ok = file:write_file(Filename, 
         <<"Nonterminals nt. Terminals t. 
            Rootsymbol t. nt -> t.">>),
    ?line {error,[{_,[{2,yecc,{bad_rootsymbol,t}}]}],[]} = 
        yecc:file(Filename, Ret),

    %% Rootsymbol is not a nonterminal.
    ?line ok = file:write_file(Filename, 
         <<"Nonterminals nt. Terminals t. 
            Rootsymbol t. nt -> t.">>),
    ?line {error,[{_,[{2,yecc,{bad_rootsymbol,t}}]}],[]} = 
        yecc:file(Filename, Ret),

    %% Endsymbol is a nonterminal.
    ?line ok = file:write_file(Filename, 
         <<"Nonterminals nt. Terminals t. Rootsymbol nt. 
            Endsymbol nt.
            nt -> t.">>),
    ?line {error,[{_,[{2,yecc,{endsymbol_is_nonterminal,nt}}]}],[]} = 
        yecc:file(Filename, Ret),

    %% Endsymbol is a terminal.
    ?line ok = file:write_file(Filename, 
         <<"Nonterminals nt. Terminals t. Rootsymbol nt. 
            Endsymbol t.
            nt -> t.">>),
    ?line {error,[{_,[{2,yecc,{endsymbol_is_terminal,t}}]}],[]} = 
        yecc:file(Filename, Ret),

    %% No grammar rules.
    ?line ok = file:write_file(Filename, 
         <<"Nonterminals nt. Terminals t. Rootsymbol nt. Endsymbol e.">>),
    ?line {error,[{_,[{none,yecc,no_grammar_rules}]}],[]} = 
        yecc:file(Filename, Ret),

    %% Bad declaration.
    ?line ok = file:write_file(Filename, 
         <<"Nonterminals nt. Terminals t. Rootsymbol nt. Endsymbol e.
            nt -> t. e e.">>),
    ?line {ok,_,[{_,[{2,yecc,bad_declaration}]}]} = 
        yecc:file(Filename, Ret),

    %% Bad declaration.
    ?line ok = file:write_file(Filename, 
         <<"Nonterminals nt. Terminals t. 
            Rootsymbol nt nt. Rootsymbol nt. Endsymbol e.
            nt -> t.">>),
    ?line {ok,_,[{_,[{2,yecc,bad_declaration}]}]} = 
        yecc:file(Filename, Ret),

    %% Syntax error found by yeccparser.
    ?line ok = file:write_file(Filename, 
         <<"Nonterminals nt. Terminals t. Rootsymbol nt. Endsymbol e.
            a - a.">>),
    ?line {error,[{_,[{2,yecc,{error,_yeccparser,_}}]}],[]} =
        yecc:file(Filename, Ret),

    %% Syntax error: unknown nonterminal.
    ?line ok = file:write_file(Filename, 
         <<"Nonterminals nt. Terminals t. Rootsymbol nt. Endsymbol e.
            'unknown ' -> t.">>),
    ?line {error,[{_,[{2,yecc,{undefined_nonterminal,'unknown '}}]}],[]} = 
        yecc:file(Filename, Ret),

    %% Undefined rhs symbols. Note quotes in output.
    ?line ok = file:write_file(Filename, 
         <<"Nonterminals Nonterminals nt. 
            Terminals t Terminals. 
            Rootsymbol nt. 
            Endsymbol e.
            nt -> Nonterminals.
            Nonterminals -> Terminals receive foo 45 
                            '17' 'a b'.">>),
    ?line {error,[{_,[{6,yecc,{undefined_symbol,45}},
                      {6,yecc,{undefined_symbol,foo}},
                      {6,yecc,{undefined_symbol,'receive'}},
                      {7,yecc,{undefined_symbol,'17'}},
                      {7,yecc,{undefined_symbol,'a b'}}]}],[]} = 
        yecc:file(Filename, Ret),

    %% '$empty' used early, before Terminals. OK.
    ?line ok = file:write_file(Filename, 
         <<"Nonterminals nt. 
            nt -> '$empty'.
            Terminals t. 
            Rootsymbol nt. 
            Endsymbol e.">>),
    ?line {ok,_,[{_,[{3,yecc,{unused_terminal,t}}]}]} = 
        yecc:file(Filename, Ret),

    %% Illegal use of '$empty'.
    ?line ok = file:write_file(Filename, 
         <<"Nonterminals nt nt2. 
            Terminals t. 
            Rootsymbol nt. 
            Endsymbol e.
            nt -> t.
            nt2 -> t '$empty'.">>),
    ?line {error,[{_,[{6,yecc,illegal_empty}]}],[]} = 
        yecc:file(Filename, Ret),

    ParserFile3 = [{parserfile, Parserfile1}],

    %% Bad Erlang expression in action. Changed in OTP-7224.
    ?line ok = file:write_file(Filename,
         <<"Nonterminals nt. 
            Terminals t. 
            Rootsymbol nt. 
            Endsymbol e.
            nt -> t : a bad code.">>),
    ?line {ok, _, []} = yecc:file(Filename, ParserFile3 ++ Ret),

    SzYeccPre = yeccpre_size(),
    %% Note: checking the line numbers. Changes when yeccpre.hrl changes.
    fun() ->
            ?line {error,[{_,[{5,_,["syntax error before: ","bad"]}]},
                          {_,[{L1,_,{undefined_function,{yeccpars2_2_,1}}},
                              {L2,_,{bad_inline,{yeccpars2_2_,1}}}]}],
                   []} = compile:file(Parserfile1, [basic_validation,return]),
            ?line L1 = 28 + SzYeccPre,
            ?line L2 = 35 + SzYeccPre
    end(),

    %% Bad macro in action. OTP-7224.
    ?line ok = file:write_file(Filename,
         <<"Nonterminals nt. 
            Terminals t. 
            Rootsymbol nt. 
            Endsymbol e.
            nt -> t : ?F(3).">>),
    ?line {ok, _, []} = yecc:file(Filename, ParserFile3 ++ Ret),
    %% Note: checking the line numbers. Changes when yeccpre.hrl changes.
    fun() ->
            ?line {error,[{_,[{5,_,{undefined,'F',1}}]},
                          {_,[{L1,_,{undefined_function,{yeccpars2_2_,1}}},
                              {L2,_,{bad_inline,{yeccpars2_2_,1}}}]}],
                   []} = compile:file(Parserfile1, [basic_validation,return]),
            ?line L1 = 28 + SzYeccPre,
            ?line L2 = 35 + SzYeccPre
    end(),

    %% Check line numbers. OTP-7224.
    ?line ok = file:write_file(Filename,
         <<"Terminals t. 
            Nonterminals nt. 
            Rootsymbol nt. 
            Endsymbol e.
            nt -> t : ?F(3).
            Erlang code.
            -define(F(X), X).
            t() ->
               bad().">>),
    ?line {ok, _, []} = yecc:file(Filename, ParserFile3 ++ Ret),
    ?line {error,[{_,[{9,_,{undefined_function,{bad,0}}}]}],
           [{_,[{8,_,{unused_function,{t,0}}}]}]} 
        = compile:file(Parserfile1, [basic_validation, return]),

    %% Terminals defined before nonterminals. (One of many checks...)
    %% Used to give an error message, but now allowed.
    ?line ok = file:write_file(Filename,
         <<"Terminals t. 
            Nonterminals nt. 
            Rootsymbol nt. 
            Endsymbol e.
            nt -> t.
            Erlang code.">>),
    ?line {ok, _, []} = yecc:file(Filename, Ret),

    %% Precedence with swapped arguments. Bad declaration.
    ?line ok = file:write_file(Filename, 
         <<"Nonterminals nt. 
            Terminals t. 
            Rootsymbol nt. 
            Endsymbol e.
            nt -> t.
            Right t. 
            Left nt 100.">>),
    ?line {ok,_,[{_,[{6,yecc,bad_declaration},{7,yecc,bad_declaration}]}]} = 
        yecc:file(Filename, Ret),

    %% Precedence with unknown operator.
    ?line ok = file:write_file(Filename, 
         <<"Nonterminals nt. 
            Terminals t. 
            Rootsymbol nt. 
            Endsymbol e.
            nt -> t.
            Unary 100 '-'.">>),
    ?line {error,[{_,[{6,yecc,{precedence_op_is_unknown,'-'}}]}],[]} = 
        yecc:file(Filename, Ret),

    %% Precedence with endsymbol operator.
    ?line ok = file:write_file(Filename, 
         <<"Nonterminals nt. 
            Terminals t. 
            Rootsymbol nt. 
            Endsymbol e.
            nt -> t.
            Unary 100 e.">>),
    ?line {error,[{_,[{6,yecc,{precedence_op_is_endsymbol,e}}]}],[]} = 
        yecc:file(Filename, Ret),

    %% Duplicated precedence.
    ?line ok = file:write_file(Filename, <<"
            Nonterminals nt. 
            Terminals t '+'. 
            Rootsymbol nt. 
            nt -> t '+' nt.
            Left 100 '+'.
            Right 200 t.
            Left 200 '+'.
            Left 200 '+'.
            Right 200 t.">>),
    ?line {error,[{_,[{8,yecc,{duplicate_precedence,'+'}},
                      {9,yecc,{duplicate_precedence,'+'}},
                      {10,yecc,{duplicate_precedence,t}}]}],
           []} = yecc:file(Filename, Ret),

    %% Duplicated nonterminal.
    ?line ok = file:write_file(Filename, 
         <<"Nonterminals 'n t' 'n t'. Terminals t. 
            Rootsymbol 'n t'. 'n t' -> t.">>),
    ?line {ok, _, [{_,[{1,yecc,{duplicate_nonterminal,'n t'}}]}]} = 
        yecc:file(Filename, Ret),
    ?line {ok, _, [{_,[{1,yecc,{duplicate_nonterminal,'n t'}}]}]} = 
        yecc:file(Filename, [return_warnings, {report, false}]),
    ?line {ok, _} = yecc:file(Filename),
    ?line {ok, _} = 
        yecc:file(Filename, [{report,false}, {return_warnings,false}]),

    %% Duplicated terminal.
    ?line ok = file:write_file(Filename, 
         <<"Nonterminals nt. Terminals 't t' 't t'. 
            Rootsymbol nt. nt -> 't t'.">>),
    ?line {ok, _, [{_,[{1,yecc,{duplicate_terminal,'t t'}}]}]} = 
       yecc:file(Filename, Ret),

    %% Two Nonterminals declarations.
    ?line ok = file:write_file(Filename, 
         <<"Nonterminals nt. Terminals t. 
            Nonterminals nt2.
            Rootsymbol nt2. nt -> t. nt2 -> nt.">>),
    ?line {error,[{_,[{2,yecc,{duplicate_declaration,'Nonterminals'}}]}],
           []} = yecc:file(Filename, Ret),

    %% Three Terminals declarations.
    ?line ok = file:write_file(Filename, 
         <<"Nonterminals nt. Terminals t. 
            Terminals t1.
            Terminals t1.
            Rootsymbol nt. nt -> t t1.">>),
    ?line {error,[{_,[{2,yecc,{duplicate_declaration,'Terminals'}},
                      {3,yecc,{duplicate_declaration,'Terminals'}}]}],
           []} = yecc:file(Filename, Ret),

    %% Two root symbols.
    ?line ok = file:write_file(Filename, 
         <<"Nonterminals nt. Terminals t. Rootsymbol t. 
            Rootsymbol nt. nt -> t.">>),
    ?line {error,[{_,[{2,yecc,{duplicate_declaration,'Rootsymbol'}}]}],[]} = 
        yecc:file(Filename, Ret),

    %% Two end symbols.
    ?line ok = file:write_file(Filename, 
         <<"Nonterminals nt. Terminals t. Rootsymbol t. 
            Endsymbol e. 
            Endsymbol e. nt -> t.">>),
    ?line {error,[{_,[{3,yecc,{duplicate_declaration,'Endsymbol'}}]}],[]} = 
        yecc:file(Filename, Ret),

    %% Two end symbols.
    ?line ok = file:write_file(Filename, 
         <<"Nonterminals nt. Terminals t. Rootsymbol t. 
            Expect 1.
            Expect 0.
            Endsymbol e. nt -> t.">>),
    ?line {error,[{_,[{3,yecc,{duplicate_declaration,'Expect'}}]}],[]} = 
        yecc:file(Filename, Ret),

    %% Some words should not be used.
    ?line ok = file:write_file(Filename, <<"
            Terminals '$empty' '$end'.
            Nonterminals '$undefined'.
            Rootsymbol '$undefined'.
            Endsymbol '$end'.
            '$undefined' -> '$empty'.
          ">>),
    ?line {error,[{_,[{2,yecc,{reserved,'$empty'}},
                      {2,yecc,{reserved,'$end'}},
                      {3,yecc,{reserved,'$undefined'}},
                      {5,yecc,{endsymbol_is_terminal,'$end'}}]}],[]} = 
        yecc:file(Filename, Ret),

    %% Undefined pseudo variable.
    ?line ok = file:write_file(Filename,<<"
              Nonterminals nt. 
              Terminals t.
              Rootsymbol nt.
              nt -> t : '$2'.
          ">>),
    ?line {error,[{_,[{5,yecc,{undefined_pseudo_variable,'$2'}}]}],[]} =
        yecc:file(Filename, Ret),
    
    %% Space in module name.
    ?line ok = file:write_file(Filename, <<"
        Nonterminals list.
        Terminals element.
        Rootsymbol list.

        list -> element : {single, '$1'}.
        list -> list element : {pair, '$1', '$2'}.

        Erlang code.

        -export([t/0]).

        t() ->
            L = [{element, 1}, {element, 2}, {element, 3}],
            {ok,_} = parse(L),
            ok.
       ">>),

    Parserfile2 = filename:join(Dir, "a \"file\""),
    %% The parser (yeccgramm.yrl) allows many symbol names...
    ?line ok = file:write_file(Filename, <<"
        Nonterminals Nonterminals Rootsymbol ':' '->'. 
        Terminals Terminals. 
        Rootsymbol Rootsymbol. 
        Endsymbol e.
        Rootsymbol -> Nonterminals ':' '->'.
        Nonterminals -> Terminals.
        ':' -> '->'.
        '->' -> Terminals.
       ">>),
    ?line {ok, _} = yecc:file(Filename, [{parserfile, Parserfile1}]),
    ?line {ok, _} = yecc:file(Filename, [{parserfile, Parserfile1},time]),
    ?line {ok,[]} = compile:file(Parserfile1, [basic_validation]),

    Quotes = <<"
            Nonterminals 
            tail try 17 42 Unused ' unused ' '42' 'fnutt\\'' receive.

            Terminals 
            ']' '|' ',' '\"hi\"' 'there\\'' 'you\\'' ACCEPT.

            Rootsymbol 17.

            Endsymbol '$end'.

            17 -> try : '$1':more([\"hi \\\" there\", '.', 3.14, % $3 , % '$1' is a module
                                    -104, ' ', ' a', '->', $a, $\t]), '$1'.

            try -> tail 'you\\'' 'fnutt\\''.

            'fnutt\\'' -> 'you\\'' ACCEPT.

            '42' -> tail.

            tail -> ']' : 
                           (fun(Strange) -> foo end)(apa).
            tail -> '|' try ']' : 
                           (fun(Strange) -> foo;
                               (_) -> 'when' % warning
                            end)('ap a', foo),
                           %% This line intentionally left blank.
                           R = #rec{},
                           A = R#rec.a,
                           <<\"hi\">> = <<\"hi\">>,
                           there.
            tail -> ',' try tail : {cons,line('$2'),'$2','$3'}.


            Erlang code.

            -export([t/0]).

            -record(rec, {a}).

            just(Some, Code) ->
                true.

            more(Code) ->
                io:format(\"~p~n\", [Code]), true.

            line(_) ->
                17.

            t() ->
                ok. % compiled successfully...

             ">>,

    ?line ok = file:write_file(Filename, Quotes),
    ?line {ok,_,[{_,
                  [{3,yecc,{unused_nonterminal,42}},
                   {3,yecc,{unused_nonterminal,' unused '}},
                   {3,yecc,{unused_nonterminal,'42'}},
                   {3,yecc,{unused_nonterminal,'Unused'}},
                   {3,yecc,{unused_nonterminal,'receive'}},
                   {6,yecc,{unused_terminal,'"hi"'}},
                   {6,yecc,{unused_terminal,'there\''}}]}]} =
        yecc:file(Filename, Ret),

    Ts = [{quotes, Quotes, default, ok}],
    ?line run(Config, Ts),

    %% Non-terminal has no rules, but is unused.
    ?line ok = file:write_file(Filename,<<"
              Nonterminals nt bad. 
              Terminals t. 
              Rootsymbol nt. 

              nt -> t : something.
          ">>),
    ?line {ok,_,[{_,[{2,yecc,{unused_nonterminal,bad}}]}]} =
        yecc:file(Filename, Ret),

    %% Non-terminal has no rules and is used.
    ?line ok = file:write_file(Filename,<<"
              Nonterminals nt bad. 
              Terminals t. 
              Rootsymbol nt. 

              nt -> t bad : something.
          ">>),
    ?line {error,[{_,[{2,yecc,{missing_syntax_rule,bad}}]}],[]} =
        yecc:file(Filename, Ret),

    %% Warning in Erlang code. With and without file attributes.
    ?line ok = file:write_file(Filename,<<"
              Nonterminals nt. 
              Terminals t. 
              Rootsymbol nt. 

              nt -> t : t(17).

              Erlang code.

              t(A) ->
                  b.
          ">>),
    ?line {ok, _, []} = 
        yecc:file(Filename, [file_attributes | Ret]),
    Opts = [basic_validation, return],
    Erlfile = filename:join(Dir, "file.erl"),
    ?line {ok,[],[{_,[{10,_,_}]}]} = compile:file(Erlfile, Opts),
    ?line {ok, _, []} = 
        yecc:file(Filename, [{file_attributes,false} | Ret]),
    ?line {ok,[],[{_,[{4,_,_}]}]} = compile:file(Erlfile, Opts),

    file:delete(Parserfile1 ++ ".erl"),
    file:delete(Parserfile2 ++ ".erl"),
    file:delete(Filename),

    ok.

compile(doc) ->
    "Check of compile/3.";
compile(suite) -> [];
compile(Config) when is_list(Config) ->
    Dir = ?privdir,
    Filename = filename:join(Dir, "file.yrl"),
    Parserfile = filename:join(Dir, "file.erl"),
    ?line ok = file:write_file(Filename, 
                               <<"Nonterminals nt. 
                                  Terminals t.
                                  Rootsymbol nt.
                                  nt -> t.">>),
    ?line error = yecc:compile(Filename, "//", #options{}),
    ?line ok = yecc:compile(Filename, Parserfile, #options{}),
    file:delete(Parserfile),
    file:delete(Filename),
    ok.

rules(doc) ->
    "Check of rules.";
rules(suite) -> [];
rules(Config) when is_list(Config) ->
    Dir = ?privdir,
    Ret = [return, {report, false}],
    Filename = filename:join(Dir, "file.yrl"),

    ?line ok = file:write_file(Filename,
      <<"Nonterminals nt. Terminals t. Rootsymbol nt. 
         nt -> t. nt -> t.">>),
    ?line {error,[{_,[{none,yecc,{conflict,_}}]}],
           [{_,[{none,yecc,{conflicts,0,1}}]}]} = 
        yecc:file(Filename, [return, report]),

    ?line ok = file:write_file(Filename, <<"
                Nonterminals A B E.
                Terminals c d f x y.
                Rootsymbol A.

                A -> B c d.
                A -> E c f.
                B -> x y.
                E -> x y.
                ">>),
    ?line {error,[{_,[{none,yecc,{conflict,_}}]}],
           [{_,[{none,yecc,{conflicts,0,1}}]}]} = 
        yecc:file(Filename, Ret),

    ?line ok = file:write_file(Filename,<<"
              Terminals t.
              Nonterminals nt.
              Rootsymbol nt.
              nt -> '$empty' : '$1'.
              nt -> t.
          ">>),
    ?line {error,[{_,[{5,yecc,{undefined_pseudo_variable,'$1'}}]}],[]} =
        yecc:file(Filename, Ret),
    
    ?line ok = file:write_file(Filename,<<"
              Terminals t.
              Nonterminals nt.
              Rootsymbol nt.
              nt -> '$empty' : '$0'.
              nt -> t.
          ">>),
    ?line {error,[{_,[{5,yecc,{undefined_pseudo_variable,'$0'}}]}],[]} =
        yecc:file(Filename, Ret),

    file:delete(Filename),
    
    %% No precedences.
    Ts = [{rules_1,<<"
              Nonterminals exp.
              Terminals number '*' '+' '(' ')'.
              Rootsymbol exp.

              exp -> exp '+' exp : {plus,'$1','$3'}.
              exp -> exp '*' exp : {times,'$1','$3'}.
              exp -> number : element(2, '$1').
              exp -> '(' exp ')' : '$2'.

              Erlang code.

              -export([t/0]).

              t() ->
                  {ok, {times, 1, {plus, 3, 5}}} = 
                    parse([{number,1}, {'*',2}, {number,3}, 
                                       {'+',4}, {number,5}]),
                  ok.
          ">>,
         default,
         ok}],
    ?line run(Config, Ts),
    ok.

examples(suite) ->
    [empty, prec, yeccpre, lalr, old_yecc, other_examples].

expect(doc) ->
    "Check of expect.";
expect(suite) -> [];
expect(Config) when is_list(Config) ->
    Dir = ?privdir,
    Ret = [return, {report, true}],
    Filename = filename:join(Dir, "file.yrl"),

    ?line ok = file:write_file(Filename, <<"
                Nonterminals e. 
                Terminals i t else. 
                Rootsymbol e. 
                Expect a.

                e -> i e t e.
                e -> i e t e else e.
                ">>),
    ?line {error,[{_,[{5,yecc,{bad_expect,a}}]}],[]} = 
        yecc:file(Filename, Ret),

    ?line ok = file:write_file(Filename, <<"
                Nonterminals e. 
                Terminals i t else. 
                Rootsymbol e. 
                Expect 1.

                e -> i e t e.
                e -> i e t e else e.
                ">>),
    ?line {ok, _, []} = yecc:file(Filename, Ret),

    ?line ok = file:write_file(Filename, <<"
                Nonterminals e. 
                Terminals i t else. 
                Rootsymbol e. 
                Expect 2.

                e -> i e t e.
                e -> i e t e else e.
                ">>),
    ?line {ok, _, [{_,[{none,yecc,{conflicts,1,0}}]}]} = 
        yecc:file(Filename, Ret),

    ?line ok = file:write_file(Filename, <<"
                Nonterminals e. 
                Terminals i t else. 
                Rootsymbol e. 
                Expect -2.

                e -> i e t e.
                e -> i e t e else e.
                ">>),
    ?line {error,[{_,[{5,yecc,{error,_yeccparser,_}}]}],[]} =
        yecc:file(Filename, Ret),

    %% States N. An undocumented declaration used for testing.
    ?line ok = file:write_file(Filename, <<"
                Nonterminals nt. 
                Terminals t.
                Rootsymbol nt.
                States 100.
                nt -> t.">>),
    ?line {ok,_,[{_, [{none,yecc,{n_states,100,3}}]}]} = 
        yecc:file(Filename, Ret),
    
    ?line ok = file:write_file(Filename, <<"
                Nonterminals nt. 
                Terminals t.
                Rootsymbol nt.
                States bad.
                nt -> t.">>),
    ?line {error,[{_,[{5,yecc,{bad_states,bad}}]}],[]} =
        yecc:file(Filename, Ret),
    
    file:delete(Filename),
    ok.

conflicts(doc) ->
    "Shift/reduce and reduce/reduce conflicts.";
conflicts(suite) -> [];
conflicts(Config) when is_list(Config) ->
    Dir = ?privdir,
    Ret = [return, {report, true}],
    Filename = filename:join(Dir, "file.yrl"),

    ?line ok = file:write_file(Filename, <<"
            Nonterminals S List Tuple Elements Element.
            Terminals '{' '}' '[' ']' ',' nil e.
            Rootsymbol S. 

            S -> '$empty' : empty.
            S -> List : '$1'.

            List -> '$empty' : [].
            List -> nil : [].
            List -> '[' Tuple ']' : {list, '$2'}.

            Tuple -> '$empty' : {}.
            Tuple -> '{' '}' : {}.
            Tuple -> '{' Elements '}' : {tuple, '$2'}.

            Elements -> '$empty' : none.
            Elements -> Elements ',' Element : {elements, '$3', '$1'}.
            Elements -> Element : '$1'.

            Element -> List : '$1'.
            Element -> Tuple : '$1'.
            Element -> e : '$1'.
           ">>),
    ?line {error,[{_,_}],[{_,[{none,yecc,{conflicts,1,5}}]}]} = 
        yecc:file(Filename, Ret),

    %% Can easily be resolved (but don't do it!).
    ?line ok = file:write_file(Filename, <<"
            Nonterminals S List Tuple Elements Element.
            Terminals '{' '}' '[' ']' ',' nil e.
            Rootsymbol S. 

            Right 100 List.

            S -> '$empty' : empty.
            S -> List : '$1'.

            List -> '$empty' : [].
            List -> nil : [].
            List -> '[' Tuple ']' : {list, '$2'}.

            Tuple -> '$empty' : {}.
            Tuple -> '{' '}' : {}.
            Tuple -> '{' Elements '}' : {tuple, '$2'}.

            Elements -> '$empty' : none.
            Elements -> Elements ',' Element : {elements, '$3', '$1'}.
            Elements -> Element : '$1'.

            Element -> List : '$1'.
            Element -> Tuple : '$1'.
            Element -> e : '$1'.
           ">>),
    ?line {ok, _, []} = 
        yecc:file(Filename, Ret),

    file:delete(Filename),
    ok.

empty(doc) ->
    "'$empty'.";
empty(suite) -> [];
empty(Config) when is_list(Config) ->
    Ts = [{empty_1, <<"
            Nonterminals nt. 
            Terminals t. 
            Rootsymbol nt. 
            Endsymbol e.
            nt -> '$empty': nothing.
            nt -> t : something.

            Erlang code.

            -export([t/0]).

            t() ->
                {ok, nothing} = parse([{e,2}]),
                {ok, something} = parse([{t,1},{e,2}]),
                ok.">>,
           default,
           ok},
          {empty_2, <<"
            %% There used to be a bug when it came to the left
            %% corners. In this example rule 2 is a prefix of rule 1
            %% such that the rule 2 generates $empty but rule 1 does
            %% not. The old buggy code seemingly worked well too.
            Nonterminals top A B C D.
            Terminals t.
            Rootsymbol top.

            top -> A B C D : {'$1','$2','$3','$4'}.
            top -> A B C : {'$1','$2','$3'}.

            A -> '$empty' : e.
            B -> '$empty' : e.
            C -> '$empty' : e.

            D -> t : t.

            Erlang code.

            -export([t/0]).

            t() ->
                {ok,{e,e,e,t}} = parse([{t, 1}]),
                {ok,{e,e,e}} = parse([]),
                ok.
           ">>,
           default,
           ok}],
    ?line run(Config, Ts),
    ok.

prec(doc) ->
    "Precedence.";
prec(suite) -> [];
prec(Config) when is_list(Config) ->
    Dir = ?privdir,
    Ret = [return, {report, false}],
    Filename = filename:join(Dir, "file.yrl"),

    %% Don't know what 'Unary' actually means, but this is how it has
    %% always worked...
    ?line ok = file:write_file(Filename, <<"
                Nonterminals E.
                Terminals '*' '+' '=' integer.
                Rootsymbol E.

                E -> E '=' E : {op, '=', '$1', '$3'}.
                E -> E '*' E : {op, '*', '$1', '$3'}.
                E -> E '+' E  : {op, '+', '$1', '$3'}.
                E -> integer : '$1'.

                Unary 100 '='.
                Left 200 '+'.
                Left 400 '*'.
              ">>),
    ?line {error,[{_,[{none,yecc,{conflict,_}}]}],
           [{_,[{none,yecc,{conflicts,1,0}}]}]} = 
        yecc:file(Filename, Ret),

    Ts = [{prec_1, <<"
        Nonterminals E Expr Uminus.
        Terminals '=' '+' '*' '-' '(' ')' integer var dot.
        Rootsymbol Expr.

        Expr -> E dot : '$1'.
        E -> var '=' E : {match, line_of('$1'), '$1', '$3'}.
        E -> E '+' E : {op, line_of('$1'), '+', '$1', '$3'}.
        E -> E '-' E : {op, line_of('$1'), '-', '$1', '$3'}.
        E -> E '*' E : {op, line_of('$1'), '*', '$1', '$3'}.
        E -> Uminus : '$1'.
        E -> '(' E ')' : '$2'.
        E -> integer : '$1'.

        Uminus -> '-' E :   {op, line_of('$1'), '-', '$2'}.

        Right 200 '='.
        Left 300 '+'.
        Left 300 '-'.
        Left 400 '*'.
        Unary 500 Uminus.

        Erlang code.

        -export([t/0, t/1]).

        line_of(Token) ->
            element(2, Token).

        t() ->
            {ok, -56} = t(\"A = (4 + 3) * - 8. \"),
            {ok, 28} = t(\"A = 4 + B=3 * 8. \"),
            {ok, 28} = t(\"4 - 3 * - 8. \"),
            {ok, -20} = t(\"4 - 3 * 8. \"),
            {ok, 2} = t(\"1 - - 1.\"),
            ok.

        t(S) ->
            case erl_scan:string(S) of
                {ok, Tokens, _Line} ->
                    case parse(Tokens) of
                        {ok, Expr} ->
                            case catch erl_eval:expr(Expr, []) of
                                {value, Value, _Bs} ->
                                    {ok, Value};
                                {'EXIT', Reason} ->
                                    {error, Reason}
                            end;
                        Error ->
                            Error
                    end;
                Error ->
                    Error
            end.
        ">>,
        default,
        ok},

          {prec_2, <<"
        Nonterminals E uminus.
        Terminals '*' '-' integer.
        Rootsymbol E.

        E -> E '-' E : {op, '-', '$1', '$3'}.
        E -> E '*' E : {op, '*', '$1', '$3'}.
        E -> uminus: '$1'.
        E -> integer : '$1'.

        uminus -> '-' E :  {op, '-', '$2'}.

        Left 200 '-'.
        Left 400 '*'.
        Unary 500 uminus.

        Erlang code.

        -export([t/0]).

        t() ->
            %% This used to be parsed -(4 * 3), but that has been corrected:
            {ok,{op,'*',{op,'-',{integer,1,4}},{integer,3,12}}} = 
                parse([{'-',0},{integer,1,4},{'*',2},{integer,3,12}]),

            {ok,{op,'-',{op,'-',{integer,1,4}},{integer,3,12}}} = 
                parse([{'-',0},{integer,1,4},{'-',2},{integer,3,12}]),
            {ok,{op,'*',{op,'-',{op,'-',{integer,2,4}}},{integer,4,12}}} = 
                parse([{'-',0},{'-',1},{integer,2,4},{'*',3},{integer,4,12}]),
            {ok,{op,'-',{integer,1,4},{op,'-',{integer,4,12}}}} = 
                parse([{integer,1,4},{'-',2},{'-',3},{integer,4,12}]),
            ok.
        ">>,
        default,
        ok},
          {prec_3, <<"
            Nonterminals nt. 
            Terminals '(' ')' t op. 
            Rootsymbol nt. 

            Nonassoc 100 op.

            nt -> nt op nt : {'$2', '$1', '$3'}.
            nt -> '(' nt ')' : '$2'.
            nt -> t : '$1'.

            Erlang code.

            -export([t/0]).

            t() ->
                {error,{4,yecc_test,[\"syntax error before: \",\"op\"]}} = 
                    parse([{t,1},{op,2},{t,3},{op,4},{t,5}]),
                {ok,{{op,2},{t,1},{{op,5},{t,4},{t,6}}}} = 
                    parse([{t,1},{op,2},{'(',3},{t,4},{op,5},{t,6},{')',7}]),
                ok.
        ">>,
        default,
        ok},

          {prec_4, <<"
            Nonterminals E.
            Terminals '-' '+' '=' id.
            Rootsymbol E.

            E -> E '=' E : {op, '=', '$1', '$3'}.
            E -> E '+' E  : {op, '+', '$1', '$3'}.
            E -> '-' E : {op, '-', '$2'}.
            E -> id : '$1'.

            Nonassoc 100 '='.
            Right 200 '+' '-'.

            Erlang code.

            -export([t/0]).

            t() ->
                {ok,{op,'=',{id,1},{op,'-',{op,'+',{id,4},{id,6}}}}} = 
                    parse([{id,1},{'=',2},{'-',3},{id,4},{'+',5},{id,6}]),
                ok.

        ">>,
        default,
        ok}],

    ?line run(Config, Ts),
    ok.

yeccpre(doc) ->
    "Errors etc. in actions, handled by yeccpre. parse_and_scan.";
yeccpre(suite) -> [];
yeccpre(Config) when is_list(Config) ->
    Ts = [{error_1, <<"
            Nonterminals list.
            Terminals element e.
            Rootsymbol list.

            list -> element : {single, '$1'}.
            list -> list element : throw(error).
            list -> e : return_error(element(2, '$1'), bad_element).

            Erlang code.

            -export([t/0]).

            t() ->
               error = (catch parse([{element,1},{element,2}])),
               {error, {2, _, Mess}} = parse([{element,1},{e,2}]),
               ok.
           ">>,
           default,
           ok},

          {error_2, <<"
            Nonterminals list.
            Terminals element.
            Rootsymbol list.

            list -> element.

            Erlang code.

            -export([t/0, scan/1]).

            scan(How) ->
                case How of
                    thrown -> throw(thrown);
                    error -> erlang:error(error);
                    exit -> exit(exit)
                end.

            t() ->
                try parse_and_scan({yecc_test, scan, [thrown]})
                catch throw: thrown ->
                        ok
                end,
                try parse_and_scan({yecc_test, scan, [error]})
                catch error: error ->
                        ok
                end,
                try parse_and_scan({{yecc_test, scan}, [exit]})
                catch exit: exit ->
                        ok
                end,
                try parse_and_scan({fun scan/1, [thrown]})
                catch throw: thrown ->
                        ok
                end,
                ok.
           ">>,
           default,
           ok}],
       
    ?line run(Config, Ts),
    ok.

lalr(doc) ->
    "Examples of grammars that are LALR(1) but not SLR(1).";
lalr(suite) -> [];
lalr(Config) when is_list(Config) ->
    Ts = [{lalr_1, <<"
            %% http://inst.cs.berkeley.edu/~cs164/lectures/slide14a.pdf
            %% http://pages.cpsc.ucalgary.ca/~robin/class/411/LR.1.html
            %% b d . c. Shift or reduce?
            Nonterminals S A. 
            Terminals a b c d.
            Rootsymbol S.
            S -> A a : {r1,'$1','$2'}.
            S -> b A c : {r2,'$1','$2','$3'}.
            S -> d c : {r3,'$1','$2'}.
            S -> b d a : {r4,'$1','$2','$3'}.

            A -> d : {r5,'$1'}.

            Erlang code.

            -export([t/0]).

            t() ->
               %% Reduce!
               {ok,{r2,{b,1},{r5,{d,2}},{c,3}}} = parse([{b,1},{d,2},{c,3}]),
               ok.
           ">>,
           default,
           ok},
         {lalr_2, <<"
            %% http://www.cs.pitt.edu/~mock/cs2210/lectures/lecture5.pdf
            Nonterminals S L R. 
            Terminals '*' id '='.
            Rootsymbol S.
            S -> L '=' R : {r1,'$1','$3'}.
            S -> R : {r2,'$1'}.

            L -> '*' R : {r3,'$2'}.
            L -> id : {r4,'$1'}.

            R -> L : {r5,'$1'}.

            Erlang code.

            -export([t/0]).

            t() ->
               {ok,{r1,{r3,{r5,{r4,{id,1}}}},{r5,{r4,{id,3}}}}} = 
                   parse([{'*',0},{id,1},{'=',2},{id,3}]),
               ok.
           ">>,
           default,
           ok}],
    ?line run(Config, Ts),
    ok.

old_yecc(doc) ->
    "The old interface yecc:yecc/2,3,4.";
old_yecc(suite) -> [];
old_yecc(Config) when is_list(Config) ->
    Dir = ?privdir,
    Filename = filename:join(Dir, "file.yrl"),
    Parserfile = filename:join(Dir, "file.erl"),
    Mini = <<"Nonterminals nt. 
              Terminals t.
              Rootsymbol nt.
              nt -> t.">>,
    ?line ok = file:write_file(Filename, Mini),
    ?line {_, _} = yecc:yecc(Filename, Parserfile),
    ?line {_, _} = yecc:yecc(Filename, Parserfile, true),
    ?line {_, _} = yecc:yecc(Filename, Parserfile, true),
    TE = process_flag(trap_exit, true),
    ?line {'EXIT', error} = 
        (catch yecc:yecc(Filename, Parserfile, false, Parserfile)),
    _ = process_flag(trap_exit, TE),
    ok.

other_examples(doc) ->
    "Misc examples.";
other_examples(suite) -> [];
other_examples(Config) when is_list(Config) ->
    Ts = [{empty, <<"
            %% G1 from the 1974 article.

            Nonterminals LIST ELEMENT.
            Terminals ',' a b.
            Rootsymbol LIST.

            LIST -> LIST ',' ELEMENT : {'$1', '$3'}.
            LIST -> ELEMENT : '$1'.

            ELEMENT -> a : '$1'.
            ELEMENT -> b : '$1'.

            Erlang code.

            -export([t/0]).

            t() ->
                L = [{a, 1}, {',', 2}, {b, 3}],
                {ok,{{a,1},{b,3}}} = parse(L),
                ok.
            ">>,
           default,
           ok}],
    ?line run(Config, Ts),
    ok.

bugs(suite) ->
    [otp_5369, otp_6362, otp_7945, otp_8483, otp_8486].

otp_5369(doc) ->
    "OTP-5369. A bug in parse_and_scan reported on erlang questions.";
otp_5369(suite) -> [];
otp_5369(Config) when is_list(Config) ->
    Ts = [{otp_5369,<<"
        Nonterminals list.
        Terminals element.
        Rootsymbol list.

        list -> element : {single, '$1'}.
        list -> list element : {pair, '$1', '$2'}.

        Erlang code.

        -export([t/0, scan/1]).

        scan(Lexer) ->
            Lexer ! {scan, self()},
            receive
                {ok, Lexer, [Token], Position} ->
                    {ok, [Token], Position};
                {eof, Lexer, Position} ->
                    {eof, Position}
            end.

        make_scanner(L) ->
            spawn_link(fun() ->
                               loop(L)
                       end).

        loop([]) ->
            receive
                {scan, Pid} ->
                    Pid ! {eof, self(), 1000}
            end;
        loop([Token = {_, P}|T]) ->
            receive
                {scan, Pid} ->
                    Pid ! {ok, self(), [Token], P},
                    loop(T)
            end.

        t(L) ->
            case parse_and_scan({yecc_test, scan, [make_scanner(L)]}) of
                {ok, _Result} ->
                    ok;
                {error, {_LineNumber, Module, Message}} ->
                    _M = apply(Module, format_error, [Message]),
                    not_ok
            end.

        t() ->
            not_ok = t([]), %% This test has been added afterwards.
                            %% OTP-5369 did not fix this bug!
            L = [{element, 1},
                 {element, 2},
                 {element, 3}],
            t(L).
      ">>,
      default,
      ok}],
    ?line run(Config, Ts),
    ok.

otp_6362(doc) ->
    "OTP-6362. A precedence declaration bug reported on erlang questions.";
otp_6362(suite) -> [];
otp_6362(Config) when is_list(Config) ->
    Ts = [{otp_6362_1,<<"
        Nonterminals cmp compare expr.
        Terminals 'string' '>' '='.
        Rootsymbol compare.
        Nonassoc 250 cmp.
        compare -> expr cmp expr     : {cmp, '$2', '$1', '$3'}.
        compare -> expr              : {cmp, '==', '$1', {string, \"TRUE\"}}.
        cmp -> '>' '='               : '>='.
        cmp -> '>'                   : '>'.
        expr -> 'string'             : '$1'.

        Erlang code.

        -export([t/0]).

        t() ->
            {ok,{cmp,'>',{string,1},{string,3}}} = 
                parse([{string,1}, {'>', 2}, {string,3}]),
            ok.
      ">>,
      default,
      ok}],
    ?line run(Config, Ts),

    Dir = ?privdir,
    %% Report errors. Very simple test of format_error/1.
    Ret = [return, {report, true}],
    Filename = filename:join(Dir, "file.yrl"),

    %% An error introduced due to this ticket. Terminals can be
    %% assigned conflicting precedences, which cannot be resolved.
    ?line ok = file:write_file(Filename,<<"
            Nonterminals cmp compare expr fopp.
            Terminals string '>' '='.
            Rootsymbol compare.
            Nonassoc 250 cmp.
            Left 300 '>'.

            compare -> expr cmp expr	: {cmp, '$2', '$1', '$3'}.
            compare -> expr fopp expr	: {cmp, '$2', '$1', '$3'}.
            compare -> expr '=' expr        : '$1'.
            compare -> expr 		: {cmp, '==', '$1', {string, foo}}.
            cmp -> '>' '='			: '>='.
            cmp -> '>'			: '>'.
            expr -> string  		: '$1'.

            fopp -> '>' '='			: '>='.
            fopp -> '>'			: '>'.">>),

    Ret = [return, {report, true}],
    ?line {error,[{_,[{none,yecc,{conflict,_}}]}],[]} = 
        yecc:file(Filename, Ret),
    file:delete(Filename),
    ok.

my_yeccpre() ->
  %% Version 1.1.
  <<"parse(Tokens) ->
         yeccpars0(Tokens, false).

     parse_and_scan({F, A}) -> % Fun or {M, F}
         yeccpars0([], {F, A});
     parse_and_scan({M, F, A}) ->
         yeccpars0([], {{M, F}, A}).

     format_error(Message) ->
         case io_lib:deep_char_list(Message) of
             true ->
                 Message;
             _ ->
                 io_lib:write(Message)
         end.

     % To be used in grammar files to throw an error message to the parser
     % toplevel. Doesn't have to be exported!
     -compile({nowarn_unused_function,{return_error,2}}).
     return_error(Line, Message) ->
         throw({error, {Line, yecc_test, Message}}).

     yeccpars0(Tokens, MFA) ->
         try yeccpars1(Tokens, MFA, 0, [], [])
         catch 
             throw: {error, {_Line, yecc_test, _M}} = Error -> 
                        Error % probably from return_error/1
         end.

     % Don't change yeccpars1/6 too much, it is called recursively by yeccpars2/8!
    yeccpars1([Token | Tokens], Tokenizer, State, States, Vstack) ->
        yeccpars2(State, element(1, Token), States, Vstack, Token, Tokens,
                  Tokenizer);
    yeccpars1([], {F, A}, State, States, Vstack) ->
        case apply(F, A) of
            {ok, Tokens, _Endline} ->
                yeccpars1(Tokens, {F, A}, State, States, Vstack);
            {eof, _Endline} ->
                yeccpars1([], false, State, States, Vstack);
            {error, Descriptor, _Endline} ->
                {error, Descriptor}
        end;
    yeccpars1([], false, State, States, Vstack) ->
        yeccpars2(State, '$end', States, Vstack, {'$end', 999999}, [], false).

     % For internal use only.
     yeccerror(Token) ->
         {error,
          {element(2, Token), yecc_test,
           [\"syntax error before: \", yecctoken2string(Token)]}}.

     yecctoken2string({atom, _, A}) -> io_lib:write(A);
     yecctoken2string({integer,_,N}) -> io_lib:write(N);
     yecctoken2string({float,_,F}) -> io_lib:write(F);
     yecctoken2string({char,_,C}) -> io_lib:write_char(C);
     yecctoken2string({var,_,V}) -> io_lib:format('~s', [V]);
     yecctoken2string({string,_,S}) -> io_lib:write_string(S);
     yecctoken2string({reserved_symbol, _, A}) -> io_lib:format('~w', [A]);
     yecctoken2string({_Cat, _, Val}) -> io_lib:format('~w', [Val]);
     yecctoken2string({'dot', _}) -> io_lib:format('~w', ['.']);
     yecctoken2string({'$end', _}) ->
         [];
     yecctoken2string({Other, _}) when is_atom(Other) ->
         io_lib:format('~w', [Other]);
     yecctoken2string(Other) ->
         io_lib:write(Other).
">>.

otp_7945(doc) ->
    "OTP-7945. A bug introduced in R13A.";
otp_7945(suite) -> [];
otp_7945(Config) when is_list(Config) ->
    ?line {error,_} = erl_parse:parse([{atom,3,foo},{'.',2,9,9}]),
    ok.

otp_8483(doc) ->
    "OTP-8483. reduce/accept conflict";
otp_8483(suite) -> [];
otp_8483(Config) when is_list(Config) ->
    Dir = ?privdir,
    Input = filename:join(Dir, "bug.yrl"),

    Bug1 = <<"Nonterminals elem seq.
              Terminals 'foo'.
              Rootsymbol elem.
              elem -> 'foo'.
              elem -> seq.
              seq -> elem.
              seq -> seq elem.">>,
    ?line ok = file:write_file(Input, Bug1),
    Ret = [return, {report, true}],
    ?line {error,[{_,[{none,yecc,{conflict,_}},
                      {none,yecc,{conflict,_}},
                      {none,yecc,{conflict,_}}]}],
           [{_,[{none,yecc,{conflicts,1,3}}]}]} = 
        yecc:file(Input, Ret),
    file:delete(Input),
    ok.

otp_8486(doc) ->
    "OTP-8486.";
otp_8486(suite) -> [];
otp_8486(Config) when is_list(Config) ->
    Ts = [{otp_8486,<<"
           Nonterminals boolean command.
           Terminals '(' ')' if then else true and or skip while do.
           Rootsymbol command.
           Left 100 or.
           Left 200 and.
           boolean -> '(' boolean ')' : '$2'.
           boolean -> 'true' : b.
           boolean -> boolean 'and' boolean : {a,'$1','$3'}.
           boolean -> boolean 'or' boolean : {o,'$1','$3'}.
           command -> 'skip' : s.
           command -> 'if' boolean 'then' command 'else' command : 
                                  {i,'$2','$4','$6'}.
           command -> 'while' boolean 'do' command : {w,'$2','$4'}.

           Erlang code.
           -export([t/0]).
           t() ->
               {ok,{i,{o,b,b},s,s}} =
                   parse([{'if',1},{'true',1},{'or',1},{'true',1},
                          {'then',1},{'skip',1},{'else',1},{'skip',1}]),
               ok.
          ">>,default,ok}],
    ?line run(Config, Ts),
    ok.

improvements(suite) ->
    [otp_7292, otp_7969].

otp_7292(doc) ->
    "OTP-7292. Header declarations for edoc.";
otp_7292(suite) -> [];
otp_7292(Config) when is_list(Config) ->
    Dir = ?privdir,
    Filename = filename:join(Dir, "file.yrl"),
    Parserfile1 = filename:join(Dir, "a file"),

    Contents =  <<"Nonterminals nt. 
                   Terminals t. 
                   Rootsymbol nt. 
                   Endsymbol e.
                   nt -> t : a bad code.

                   Header \"%% copyright bla bla bla\"
                   \"%% @private\" \"%% foo\"
                   \"%% @author X.Y.\".

                   Erlang code.

                   %% @private
                   %% etc.

                   foo() ->
                       bar. ">>,

    %% Check that correct line number is used in messages.
    ?line ok = file:write_file(Filename, Contents),
    ParserFile3 = [{parserfile, Parserfile1}],
    Ret = [return, {report, true}],
    ?line {ok, _, []} = yecc:file(Filename, ParserFile3 ++ Ret),

    %% Note: checking the line numbers. Changes when yeccpre.hrl changes.
    fun() ->
            SzYeccPre = yeccpre_size(),
            ?line {error,
                   [{_,[{5,_,["syntax error before: ","bad"]}]},
                    {_,[{L1,_,{undefined_function,{yeccpars2_2_,1}}},
                        {L2,_,{bad_inline,{yeccpars2_2_,1}}}]}],
                   [{_,[{16,_,{unused_function,{foo,0}}}]}]} = 
                compile:file(Parserfile1, [basic_validation, return]),
            ?line L1 = 38 + SzYeccPre,
            ?line L2 = 45 + SzYeccPre
    end(),

    YeccPre = filename:join(Dir, "yeccpre.hrl"),
    ?line ok = file:write_file(YeccPre, 
       [<<"-export([parse/1, parse_and_scan/1, format_error/1]).\n">>,
        yeccpre_v1_2()]),
    Inc = [{includefile,YeccPre}],
    ?line {ok, _, []} = yecc:file(Filename, ParserFile3 ++ Inc ++ Ret),
    fun() ->
            SzYeccPre = yeccpre_size(YeccPre),
            ?line {error,
                   [{_,[{5,_,["syntax error before: ","bad"]}]},
                    {_,[{L1,_,{undefined_function,{yeccpars2_2_,1}}},
                        {L2,_,{bad_inline,{yeccpars2_2_,1}}}]}],
                   [{_,[{16,_,{unused_function,{foo,0}}}]}]} = 
                compile:file(Parserfile1, [basic_validation, return]),
            ?line L1 = 37 + SzYeccPre,
            ?line L2 = 44 + SzYeccPre
    end(),

    file:delete(YeccPre),
    file:delete(Parserfile1 ++ ".erl"),
    file:delete(Filename),

    ok.

yeccpre_v1_2() ->
    <<"
parse(Tokens) ->
    yeccpars0(Tokens, false).

parse_and_scan({F, A}) -> % Fun or {M, F}
    yeccpars0([], {F, A});
parse_and_scan({M, F, A}) ->
    yeccpars0([], {{M, F}, A}).

format_error(Message) ->
    case io_lib:deep_char_list(Message) of
	true ->
	    Message;
	_ ->
	    io_lib:write(Message)
    end.

-define(CODE_VERSION, \"1.2\").

yeccpars0(Tokens, MFA) ->
    try yeccpars1(Tokens, MFA, 0, [], [])
    catch 
        error: Error ->
            Stacktrace = erlang:get_stacktrace(),
            try yecc_error_type(Error, Stacktrace) of
                {syntax_error, Token} ->
                    yeccerror(Token);
                {missing_in_goto_table=Tag, State} ->
                    Desc = {State, Tag},
                    erlang:raise(error, {yecc_bug, ?CODE_VERSION, Desc},
                                Stacktrace);
                {missing_in_goto_table=Tag, Symbol, State} ->
                    Desc = {Symbol, State, Tag},
                    erlang:raise(error, {yecc_bug, ?CODE_VERSION, Desc},
                                Stacktrace)
            catch _:_ -> erlang:raise(error, Error, Stacktrace)
            end;
        throw: {error, {_Line, ?MODULE, _M}} = Error -> 
            Error % probably from return_error/2
    end.

yecc_error_type(function_clause, [{?MODULE,F,[_,_,_,_,Token,_,_]} | _]) ->
    \"yeccpars2\" ++ _ = atom_to_list(F),
    {syntax_error, Token};
yecc_error_type({case_clause,{State}}, [{?MODULE,yeccpars2,_}|_]) ->
    %% Inlined goto-function
    {missing_in_goto_table, State};
yecc_error_type(function_clause, [{?MODULE,F,[State]}|_]) ->
    \"yeccgoto_\" ++ SymbolL = atom_to_list(F),
    {ok,[{atom,_,Symbol}]} = erl_scan:string(SymbolL),
    {missing_in_goto_table, Symbol, State}.

yeccpars1([Token | Tokens], Tokenizer, State, States, Vstack) ->
    yeccpars2(State, element(1, Token), States, Vstack, Token, Tokens, 
              Tokenizer);
yeccpars1([], {F, A}, State, States, Vstack) ->
    case apply(F, A) of
        {ok, Tokens, _Endline} ->
	    yeccpars1(Tokens, {F, A}, State, States, Vstack);
        {eof, _Endline} ->
            yeccpars1([], false, State, States, Vstack);
        {error, Descriptor, _Endline} ->
            {error, Descriptor}
    end;
yeccpars1([], false, State, States, Vstack) ->
    yeccpars2(State, '$end', States, Vstack, {'$end', 999999}, [], false).

yeccpars1(State1, State, States, Vstack, Stack1, [Token | Tokens], 
          Tokenizer) ->
    yeccpars2(State, element(1, Token), [State1 | States],
              [Stack1 | Vstack], Token, Tokens, Tokenizer);
yeccpars1(State1, State, States, Vstack, Stack1, [], {F, A}) ->
    case apply(F, A) of
        {ok, Tokens, _Endline} ->
	    yeccpars1(State1, State, States, Vstack, Stack1, Tokens, {F, A});
        {eof, _Endline} ->
            yeccpars1(State1, State, States, Vstack, Stack1, [], false);
        {error, Descriptor, _Endline} ->
            {error, Descriptor}
    end;
yeccpars1(State1, State, States, Vstack, Stack1, [], false) ->
    yeccpars2(State, '$end', [State1 | States], [Stack1 | Vstack],
              {'$end', 999999}, [], false).

yeccerror(Token) ->
    {error,
     {element(2, Token), ?MODULE,
      [\"syntax error before: \", yecctoken2string(Token)]}}.

yecctoken2string({atom, _, A}) -> io_lib:write(A);
yecctoken2string({integer,_,N}) -> io_lib:write(N);
yecctoken2string({float,_,F}) -> io_lib:write(F);
yecctoken2string({char,_,C}) -> io_lib:write_char(C);
yecctoken2string({var,_,V}) -> io_lib:format('~s', [V]);
yecctoken2string({string,_,S}) -> io_lib:write_string(S);
yecctoken2string({reserved_symbol, _, A}) -> io_lib:format('~w', [A]);
yecctoken2string({_Cat, _, Val}) -> io_lib:format('~w', [Val]);
yecctoken2string({'dot', _}) -> io_lib:format('~w', ['.']);
yecctoken2string({'$end', _}) ->
    [];
yecctoken2string({Other, _}) when is_atom(Other) ->
    io_lib:format('~w', [Other]);
yecctoken2string(Other) ->
    io_lib:write(Other).
">>.

run(Config, Tests) ->
    F = fun({N,P,Pre,E}) ->
                case catch run_test(Config, P, Pre) of
                    E -> 
                        ok;
                    Bad -> 
                        ?t:format("~nTest ~p failed. Expected~n  ~p~n"
                                  "but got~n  ~p~n", [N, E, Bad]),
			fail()
                end
        end,
    lists:foreach(F, Tests).

otp_7969(doc) ->
    "OTP-7969. Interface to the I/O protocol..";
otp_7969(suite) -> [];
otp_7969(Config) when is_list(Config) ->
    ?line {ok,Ts1,_} =
        erl_scan:string("'foo\nbar'", {1,1}, [text]),
    ?line {error,{2,_,["syntax error before: ",[]]}} = erl_parse:parse(Ts1),

    ?line {ok,Ts1_1,_} = erl_scan:string("'foo\nbar'", 1, [text]),
    ?line {error,{2,_,["syntax error before: ",[]]}} = erl_parse:parse(Ts1_1),

    ?line {ok,Ts2,_EndLocation} =
        erl_scan:string("'foo\nbar'", {1,1}, []),
    %% Can't do better than report possibly wrong line:
    ?line {error,{1,_,["syntax error before: ",[]]}} = erl_parse:parse(Ts2),

    ?line {ok, Ts11, _}=R1 = erl_scan:string("f() -> a."),
    ?line F1 = fun() -> {ok,Ts11 ++ [{'$end',2}],2} end,
    ?line{ok,{function,1,f,0,[{clause,1,[],[],[{atom,1,a}]}]}} =
        erl_parse:parse_and_scan({F1, []}),
    ?line F2 = fun() -> erl_scan:string("f() -> ,") end,
    ?line {error,{1,erl_parse,_}} = erl_parse:parse_and_scan({F2, []}),
    ?line F3 = fun() -> case erase(foo) of
                            bar -> 
                                {ok,[{'$end',2}],3};
                            undefined ->
                                put(foo,bar), R1
                        end
               end,
    ?line {ok,{function,1,f,0,[{clause,1,[],[],[{atom,1,a}]}]}} =
        erl_parse:parse_and_scan({F3,[]}),
    F4 = fun() -> {error, {1, ?MODULE, bad}, 2} end,
    ?line {error, {1,?MODULE,bad}} = erl_parse:parse_and_scan({F4, []}),
    F5 = fun() -> {eof, 3} end,
    ?line {error,{3,erl_parse,_}} = erl_parse:parse_and_scan({F5, []}),
    ?line {error,{999999,erl_parse,_}} = erl_parse:parse([]),
    ?line {ok, Ts21, EL} = erl_scan:string("f() -> a; g() -> b. ", {1,1}),
    ?line F6 = fun() -> {ok, Ts21, EL} end,
    ?line {error,{{1,11},erl_parse,_}} = erl_parse:parse_and_scan({F6, []}),
    ok.

yeccpre_size() ->
    yeccpre_size(default_yeccpre()).

yeccpre_size(File) ->
    n_lines(File).
    
default_yeccpre() ->
    filename:join([code:lib_dir(parsetools),"include","yeccpre.hrl"]).

n_lines(File) ->
    {ok, Bin} = file:read_file(File),
    length([C || C=$\n <- binary_to_list(Bin)]).

run_test(Config, Def, Pre) ->
    %% io:format("testing ~s~n", [binary_to_list(Def)]),
    DefFile = 'yecc_test.yrl',
    Filename = 'yecc_test.erl',
    DataDir = ?privdir,
    YrlFile = filename:join(DataDir, DefFile),
    ErlFile = filename:join(DataDir, Filename),
    Opts = [return, warn_unused_vars,{outdir,DataDir}],
    ok = file:write_file(YrlFile, Def),
    YOpts = [return, {report, false} | 
             case Pre of
                 default ->
                     [];
                 _ ->
                     [{includefile,Pre}]
             end],
    P0 = pps(),
    YRet = yecc:file(YrlFile, [verbose | YOpts]),
    case {pps(), YRet} of
        {P0, {ok, _Outfile, _YWs}} ->
                 case compile:file(ErlFile, Opts) of
                     {ok, _M, _Ws} -> 
                         AbsFile = filename:rootname(ErlFile, ".erl"),
                         Mod = yecc_test,
                         code:purge(Mod),
                         code:load_abs(AbsFile, Mod),
                         Mod:t();
                         %% warnings(ErlFile, Ws);
                     {error, [{ErlFile,Es}], []} -> {error, Es, []};
                     {error, [{ErlFile,Es}], [{ErlFile,Ws}]} -> {error, Es, Ws};
                     Error  -> Error
                 end;
        {P0, {error, [{YrlFile,YEs}], []}} -> {error, YEs, []};
        {P0, {error, [{YrlFile,YEs}], [{YrlFile,YWs}]}} -> {error, YEs, YWs};
        {P0, YError} -> YError;
        {P, _} ->
            io:format("failure, got ~p~n, expected ~p\n", [P, P0]),
            fail()
    end.

extract(File, {error, Es, Ws}) ->
    {errors, extract(File, Es), extract(File, Ws)};    
extract(File, Ts) ->
    lists:append([T || {F, T} <- Ts,  F =:= File]).

pps() ->
    {port_list(), process_list()}.

port_list() ->
    [{P,safe_second_element(erlang:port_info(P, name))} || P <- erlang:ports()].

process_list() ->
    [{P,process_info(P, registered_name),
      safe_second_element(process_info(P, initial_call))} || 
        P <- processes(), is_process_alive(P)].

safe_second_element({_,Info}) -> Info;
safe_second_element(Other) -> Other.

fail() ->
    ?t:fail().
