%% Copyright (c) 2008-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_env.erl %% Author : Robert Virding %% Purpose : Lisp Flavoured Erlang environment functions. -module(lfe_env). -export([new/0,add_env/2, get_vars/1,clr_vars/1,set_vars/2,fold_vars/3, get_funs/1,clr_funs/1,set_funs/2,fold_funs/3,fold_macros/3, get_recs/1,clr_recs/1,set_recs/2,fold_recs/3]). -export([add_vbinding/3,add_vbindings/2,is_vbound/2,get_vbinding/2, fetch_vbinding/2,del_vbinding/2]). -export([add_fbinding/4,add_fbindings/2, is_fbound/3,get_fbinding/3,add_ibinding/5, add_mbinding/3,add_mbindings/2, is_mbound/2,get_mbinding/2]). -export([add_record/3,get_record/2]). %% Define access macros depending on whether we have maps. -ifdef(HAS_MAPS). -define(NEW(), #{}). -define(IS_KEY(K, D), maps:is_key(K, D)). -define(GET(K, D), maps:get(K, D)). -define(FIND(K, D), maps:find(K, D)). -define(PUT(K, V, D), maps:put(K, V, D)). -define(ERASE(K, D), maps:remove(K, D)). -define(FOLD(F, A, D), maps:fold(F, A, D)). -define(UPDATE(K, UPD, DEF, D), %This is slightly complex begin (fun (___K, {ok,___V}) -> maps:put(___K, UPD(___V), D); (___K, error) -> maps:put(___K, DEF, D) end)(K, maps:find(K, D)) end). -else. -define(NEW(), orddict:new()). -define(IS_KEY(K, D), orddict:is_key(K, D)). -define(GET(K, D), orddict:fetch(K, D)). -define(FIND(K, D), orddict:find(K, D)). -define(PUT(K, V, D), orddict:store(K, V, D)). -define(ERASE(K, D), orddict:erase(K, D)). -define(FOLD(F, A, D), orddict:fold(F, A, D)). -define(UPDATE(K, UPD, DEF, D), orddict:update(K, UPD, DEF, D)). -endif. %% The environment structure. -record(env, {vars=null,funs=null,recs=null}). %% -compile([export_all]). %% new() -> Env. %% add_env(Env1, Env2) -> Env. %% get_vars(Env) -> Vars. %% clr_vars(Env) -> Env. %% set_vars(Vars, Env) -> Env. %% fold_vars(Fun, Acc, Env) -> Acc. %% get_funs(Env) -> Funs. %% clr_funs(Env) -> Env. %% set_funs(Funs, Env) -> Env. %% fold_funs(Fun, Acc, Env) -> Acc. %% fold_macros(Fun, Acc, Env) -> Acc. %% get_recs(Env) -> Recs. %% clr_recs(Env) -> Env. %% set_recs(Recs, Env) -> Env. %% fold_recs(Fun, Acc, Env) -> Acc. %% add_vbinding(Name, Val, Env) -> Env. %% add_vbindings([{Name,Val}], Env) -> Env. %% is_vbound(Symb, Env) -> bool(). %% get_vbinding(Name, Env) -> {yes,Val} | no. %% fetch_vbinding(Name, Env) -> Val. %% del_vbinding(Name, Env) -> Env. %% add_fbinding(Name, Arity, Val, Env) -> Env. %% add_fbindings([{Name,Arity,Val}], Env) -> Env. %% add_ibinding(Mod, Name, Arity, LocalName, Env) -> Env. %% is_fbound(Symb, Arity, Env) -> bool(). %% get_fbinding(Name, Arity, Env) -> {yes,Val} | {yes,Mod,Name} | no. %% add_mbinding(Name, Macro, Env) -> Env. %% add_mbindings([{Name,Macro}], Env) -> Env. %% is_mbound(Symb, Env) -> bool(). %% get_mbinding(Name, Env) -> {yes,Macro} | no. %% %% The dictionary is in two parts: the variable bindings and the %% function/macro bindings, the vars and the funs fields respectively %% in the env record. The variables are kept in an orddict with the %% name as key. %% %% For the functions/macros it is a little more complex due to %% shadowing. Defining a macro effectively shadows all the function %% bindings with the same name while defining a function effectively %% shadows a macro with the same name or replaces a function %% definition with the same name and arity. Functions are kept an %% orddict with the name as key and the value is either the macro %% definition or a dict of arity definition. new() -> #env{vars=?NEW(),funs=?NEW(),recs=?NEW()}. -ifdef(HAS_MAPS). add_env(#env{vars=Vs1,funs=Fs1,recs=Rs1}, #env{vars=Vs2,funs=Fs2,recs=Rs2}) -> #env{vars=maps:merge(Vs2, Vs1), %Always take left env funs=maps:merge(Fs2, Fs1), recs=maps:merge(Rs2, Rs1)}. -else. add_env(#env{vars=Vs1,funs=Fs1,recs=Rs1}, #env{vars=Vs2,funs=Fs2,recs=Rs2}) -> Merge = fun (_, V1, _) -> V1 end, %Always take left env #env{vars=orddict:merge(Merge, Vs1, Vs2), funs=orddict:merge(Merge, Fs1, Fs2), recs=orddict:merge(Merge, Rs1, Rs2)}. -endif. %% Accessing the variable table. get_vars(Env) -> Env#env.vars. clr_vars(Env) -> Env#env{vars=?NEW()}. set_vars(Vars, Env) -> Env#env{vars=Vars}. fold_vars(Fun, Acc, Env) -> ?FOLD(Fun, Acc, Env#env.vars). %% Accessing the function/macro table. get_funs(Env) -> Env#env.funs. clr_funs(Env) -> Env#env{funs=?NEW()}. set_funs(Funs, Env) -> Env#env{funs=Funs}. %% Fold over functions and macros. fold_funs(Fun, Acc, Env) -> Ofun = fun (F, {function,Fs}, Ac) -> %Function Ffun = fun ({Ar,Def}, A) -> Fun(F, Ar, Def, A) end, lists:foldl(Ffun, Ac, Fs); (_, _, A) -> A %Macro end, ?FOLD(Ofun, Acc, Env#env.funs). fold_macros(Fun, Acc, Env) -> Ofun = fun (F, {macro,Def}, A) -> %Macro Fun(F, Def, A); (_, _, A) -> A %Function end, ?FOLD(Ofun, Acc, Env#env.funs). %% Accessing the record table. get_recs(Env) -> Env#env.recs. clr_recs(Env) -> Env#env{recs=?NEW()}. set_recs(Recs, Env) -> Env#env{recs=Recs}. fold_recs(Fun, Acc, Env) -> ?FOLD(Fun, Acc, Env#env.recs). %% Variables. add_vbinding(N, V, #env{vars=Vs}=Env) -> Env#env{vars=?PUT(N, V, Vs)}. add_vbindings(Vbs, #env{vars=Vs0}=Env) -> Vs1 = lists:foldl(fun ({N,V}, Vs) -> ?PUT(N, V, Vs) end, Vs0, Vbs), Env#env{vars=Vs1}. is_vbound(N, #env{vars=Vs}) -> ?IS_KEY(N, Vs). get_vbinding(N, #env{vars=Vs}) -> case ?FIND(N, Vs) of {ok,V} -> {yes,V}; error -> no end. fetch_vbinding(N, #env{vars=Vs}) -> ?GET(N, Vs). del_vbinding(N, #env{vars=Vs}=Env) -> Env#env{vars=?ERASE(N, Vs)}. %% Functions. add_fbinding(N, A, V, #env{funs=Fs0}=Env) -> Fs1 = add_fbinding_1(N, A, {A,V}, Fs0), Env#env{funs=Fs1}. add_fbinding_1(N, A, T, Fs) -> Def = {function,[T]}, %We KNOW! Upd = fun ({function,Fas}) -> {function,lists:keystore(A, 1, Fas, T)}; (_) -> Def %Overwrite macros end, ?UPDATE(N, Upd, Def, Fs). add_fbindings(Fbs, #env{funs=Fs0}=Env) -> Fs1 = lists:foldl(fun ({N,A,V}, Fs) -> add_fbinding_1(N, A, {A,V}, Fs) end, Fs0, Fbs), Env#env{funs=Fs1}. add_ibinding(M, R, A, L, #env{funs=Fs0}=Env) -> Fs1 = add_fbinding_1(L, A, {A,M,R}, Fs0), Env#env{funs=Fs1}. is_fbound(N, A, #env{funs=Fs}) -> case ?FIND(N, Fs) of {ok,{function,Fas}} -> case lists:keyfind(A, 1, Fas) of false -> false; _ -> true end; _ -> false %A macro or not found end. get_fbinding(N, A, #env{funs=Fs}) -> case ?FIND(N, Fs) of {ok,{function,Fas}} -> case lists:keyfind(A, 1, Fas) of {A,M,F} -> {yes,M,F}; {A,V} -> {yes,V}; false -> no end; _ -> no %A macro or not found end. %% Macros. add_mbinding(N, V, #env{funs=Fs}=Env) -> Env#env{funs=?PUT(N, {macro,V}, Fs)}. add_mbindings(Fbs, #env{funs=Fs0}=Env) -> Fs1 = lists:foldl(fun ({N,V}, Fs) -> ?PUT(N, {macro,V}, Fs) end, Fs0, Fbs), Env#env{funs=Fs1}. is_mbound(N, #env{funs=Fs}) -> case ?FIND(N, Fs) of {ok,{macro,_}} -> true; _ -> false end. get_mbinding(N, #env{funs=Fs}) -> case ?FIND(N, Fs) of {ok,{macro,V}} -> {yes,V}; _ -> no end. %% Records. add_record(R, Fs, #env{recs=Rs}=Env) -> Env#env{recs=?PUT(R, Fs, Rs)}. get_record(R, #env{recs=Rs}) -> case ?FIND(R, Rs) of {ok,Fs} -> {yes,Fs}; error -> no end.