%% Copyright (c) 2016-2020 Robert Virding
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
%% You may obtain a copy of the License at
%%
%%     http://www.apache.org/licenses/LICENSE-2.0
%%
%% Unless required by applicable law or agreed to in writing, software
%% distributed under the License is distributed on an "AS IS" BASIS,
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% See the License for the specific language governing permissions and
%% limitations under the License.

%% File    : lfe.erl
%% Author  : Robert Virding
%% Purpose : Lisp Flavoured Erlang standard library.

-module(lfe).

%% Standard lisp library.
-export([acons/3,pairlis/2,pairlis/3,
         assoc/2,'assoc-if'/2,'assoc-if-not'/2,
         rassoc/2,'rassoc-if'/2,'rassoc-if-not'/2,
         subst/3,'subst-if'/3,'subst-if-not'/3,sublis/2,
         eval/1,eval/2,
         'macro-function'/1,'macro-function'/2,
         macroexpand/1,macroexpand/2,
         'macroexpand-1'/1,'macroexpand-1'/2,
         'macroexpand-all'/1,'macroexpand-all'/2]).

-export(['LFE-EXPAND-EXPORTED-MACRO'/3]).

%% Metadata
-export([version/0]).

%% 'LFE-EXPAND-EXPORTED-MACRO'(Name, Args, Env) -> {yes,Expansion} | no.
%%  A smart implementation of this where we call the lfe_macro module
%%  to expand and check for us. Using an empty environment ensures
%%  only predefined macros are used.

'LFE-EXPAND-EXPORTED-MACRO'(Name, Args, _) ->
    lfe_macro:expand_expr([Name|Args], lfe_env:new()).

%% Standard lisp library functions.

%% Association list functions.

acons(K, V, Alist) -> [[K|V]|Alist].

pairlis(Ks, Vs) -> pairlis(Ks, Vs, []).

pairlis([K|Ks], [V|Vs], Alist) ->
    [[K|V]|pairlis(Ks, Vs, Alist)];
pairlis([], [], Alist) -> Alist.

assoc(K, [[K|_]=Pair|_]) -> Pair;
assoc(K, [_|L]) -> assoc(K, L);
assoc(_, []) -> [].

'assoc-if'(Pred, [[K|_]=Pair|L]) ->
    case Pred(K) of
        true -> Pair;
        false -> 'assoc-if'(Pred, L)
    end;
'assoc-if'(_, []) -> [].

'assoc-if-not'(Pred, [[K|_]=Pair|L]) ->
    case Pred(K) of
        false -> Pair;
        true -> 'assoc-if-not'(Pred, L)
    end;
'assoc-if-not'(_, []) -> [].

rassoc(V, [[_|V]=Pair|_]) -> Pair;
rassoc(V, [_|L]) -> rassoc(V, L);
rassoc(_, []) -> [].

'rassoc-if'(Pred, [[_|V]=Pair|L]) ->
    case Pred(V) of
        true -> Pair;
        false -> 'rassoc-if'(Pred, L)
    end;
'rassoc-if'(_, []) -> [].

'rassoc-if-not'(Pred, [[_|V]=Pair|L]) ->
    case Pred(V) of
        false -> Pair;
        true -> 'rassoc-if-not'(Pred, L)
    end;
'rassoc-if-not'(_, []) -> [].

%% subst(New, Old, Tree) -> Tree.

subst(New, Old, Old) -> New;
subst(New, Old, [H|T]) ->
    [subst(New, Old,H)|subst(New, Old, T)];
subst(_, _, Tree) -> Tree.

%% subst-if(New, Test, Tree) -> Tree.

'subst-if'(New, Test, Tree) ->
    case Test(Tree) of
        true -> New;
        false ->
            case Tree of
                [H|T] ->
                    ['subst-if'(New, Test, H)|'subst-if'(New, Test, T)];
                _ -> Tree
            end
    end.

%% subst-if-not(New, Test, Tree) -> Tree.

'subst-if-not'(New, Test, Tree) ->
    case Test(Tree) of
        false -> New;
        true ->
            case Tree of
                [H|T] ->
                    ['subst-if-not'(New, Test, H)|'subst-if-not'(New, Test, T)];
                _ -> Tree
            end
    end.

%% sublis(AList, Tree) -> Tree.

sublis(Alist, Tree) ->
    case assoc(Tree, Alist) of
        [_|New] -> New;                         %Found it
        [] ->                                   %Not there
            case Tree of
                [H|T] ->
                    [sublis(Alist, H)|sublis(Alist, T)];
                _ -> Tree
            end
    end.

%%  eval(Sexpr) -> Value.
%%  macro-function(Name [,Environment]) -> Macro | [].
%%  macroexpand(Form [,Environment]) -> Expansion | Form.
%%  macroexpand-1(Form [,Environment]) -> Expansion | Form.
%%  macroexpand-all(Form [,Environment]) -> Expansion | Form.

eval(Sexpr) -> eval(Sexpr, lfe_env:new()).      %Empty environment.
eval(Sexpr, Env) -> lfe_eval:expr(Sexpr, Env).

'macro-function'(Symb) -> 'macro-function'(Symb, lfe_env:new()).
'macro-function'(Symb, Env) ->
    case lfe_env:get_mbinding(Symb, Env) of
        {yes,Macro} ->
            Macro;
        no -> []
    end.

macroexpand(Form) -> macroexpand(Form, lfe_env:new()).
macroexpand(Form, Env) ->
    case lfe_macro:expand_expr(Form, Env) of
        {yes,Exp} -> Exp;
        no -> Form
    end.

'macroexpand-1'(Form) -> 'macroexpand-1'(Form, lfe_env:new()).
'macroexpand-1'(Form, Env) ->
    case lfe_macro:expand_expr_1(Form, Env) of
        {yes,Exp} -> Exp;
        no -> Form
    end.

'macroexpand-all'(Form) -> 'macroexpand-all'(Form, lfe_env:new()).
'macroexpand-all'(Form, Env) -> lfe_macro:expand_expr_all(Form, Env).

%% Metadata

version() ->
    {ok, [App]} = file:consult(code:where_is_file("lfe.app")),
    proplists:get_value(vsn, element(3, App)).
