-module(egg_scanner).
-export([scan/1, scan/3]).

-define(Spec, ['dot', '->','(', ')', '{', '}', '*', 'when', ',',
               '@', '~', '\\', '::', '=', '&', '?', '>',
               'and', 'or', 'andalso', 'orelse', '<!', '!>']).

scan(In) -> scan(In, '', 1).
scan(In, Prompt, Line1) ->
    case catch io:scan_erl_form(In, Prompt, Line1) of
	{eof, Line2} ->
            {eof, Line2};
	{ok, Tokens, Line2} ->
	    case Tokens of
		[] -> scan(In, Prompt, Line2);
		_  -> {ok, lex(Tokens), Line2}
	    end;
	{error, Descriptor, Line2} ->
	    {error, Descriptor, Line2};
	{'EXIT', Reason} ->
	    io:format('Scanner error while scanning input line ~w~n', [Line1]),
	    exit(Reason)
    end.

lex(Tokens) -> lists:map(fun process/1, replace_getters(replaces(Tokens))).

process(V = {Atom, _}) ->
    case lists:member(Atom, ?Spec) of
        true  -> V;
        false -> reserved(V)
    end;
process({Category, Line, Symbol}) -> {Category, Line, Symbol}.

reserved({Atom, Line}) ->
    Cat = case erl_scan:reserved_word(Atom) of
              true -> reserved_word;
              false -> reserved_symbol
          end,
    {Cat, Line, Atom}.

%%% ============================================================================
%%% Manipulating the token stream in order to make it parsable

%% -----------------------------------------------------------------------------
%% Replacing the begin and the end of the attribute computations
%% - `[@attr =`     to    `<! attr =` (the beginning)
%% - `, @attr =`    to    `@attr =` (the comma as separator is unneccessary)
%% - `]~`, `]::`, `]\` etc.     to     `!>` (the end)

replaces([{'[', L1}, {'@', _}, T3 = {atom, _, _}, T4 = {'=', _} | T]) ->
    [{'<!', L1}, T3, T4 | replaces(replace_paren(delete_commas(T)))];
replaces([H|T]) -> [H | replaces(T)];
replaces([]) -> [].

delete_commas([{',', _}, T1 = {'@', _}, T2 = {atom, _, _}, T3 = {'=', _} | T]) ->
    [T1, T2, T3 | delete_commas(T)];
delete_commas([H|T]) -> [H | delete_commas(T)];
delete_commas([]) -> [].

replace_paren(L) -> replace_paren(L, 0).
replace_paren([{']', L1}, X = {A, _} | T], 0)
  when A == '~'; A == '::'; A == 'dot'; A == '\\'; A == '[' ->
    [{'!>', L1}, X | T];
replace_paren([{']', L1}, X = {'{', _} | T], 0) -> [{'!>', L1}, {'&', L1}, X | T];
replace_paren([{']', L1}, X = {atom, _, _} | T], 0) -> [{'!>', L1}, {'&', L1}, X | T];
%% Counting the brackets...
replace_paren([H = {'[', _} | T], N) -> [H | replace_paren(T, N+1)];
replace_paren([H = {']', _} | T], N) -> [H | replace_paren(T, N-1)];
%% Nothing to do
replace_paren([H | T], N) -> [H | replace_paren(T, N)];
%% Error case
replace_paren([], _N) -> erlang:error("Expected closing bracket not found").

%% -----------------------------------------------------------------------------
%% Replacing
%% - '$N'.attr to ?get_attr(attr, '$N')
%% - '$N'.all to '$N'
%% - '$N' to ?get_attr(value, '$N')
%% (N >= 0 integer)

replace_getters([T1 = {atom, L1, A}, T2 = {'.', _}, T3 = {atom, L3, A3} | T]) ->
    Cont = [T1, T2, T3 | replace_getters(T)],
    case atom_to_list(A) of
        [$$ | Rest] ->
            try list_to_integer(Rest) of
                N when N >= 0 ->
                    case A3 of
                        all ->
                            [T1 | replace_getters(T)];
                        _ ->
                            [{'?', L1}, {atom, L1, getattr},
                             {'(', L1}, T3, {',', L1}, T1, {')',L3}
                             | replace_getters(T)]
                    end;
                _ -> Cont
            catch
                error: _ -> Cont
            end;
        _ -> Cont
    end;
replace_getters([T1 = {atom, L1, A} | T]) ->
    Cont = [T1 | replace_getters(T)],
    case atom_to_list(A) of
        [$$ | Rest] ->
            try list_to_integer(Rest) of
                N when N >= 0 ->
                    [{'?', L1}, {atom, L1, getattr},
                     {'(', L1}, {atom, L1, value}, {',', L1}, T1, {')',L1}
                     | replace_getters(T)];
                _ -> Cont
            catch
                error: _ -> Cont
            end;
        _ -> Cont
    end;
replace_getters([H|T]) -> [H | replace_getters(T)];
replace_getters([]) -> [].
