:- module(atts, [op(1199, fx, attribute),
                 term_attributed_variables/2]).

:- use_module(library(dcgs)).
:- use_module(library(error)).
:- use_module(library(terms)).

/* represent the list of attributes belonging to a variable,
   of a particular module, as a list of terms of the form
   Module:put_atts(V, ListOfAtts). */
'$default_attr_list'(Module, V) -->
    (  { Module:get_atts(V, Attributes) } ->
       '$default_attr_list'(Attributes, Module, V)
    ;  []
    ).

'$default_attr_list'([PG | PGs], Module, AttrVar) -->
    [Module:put_atts(AttrVar, PG)],
    '$default_attr_list'(PGs, Module, AttrVar).
'$default_attr_list'([], _, _) --> [].

'$absent_attr'(V, Module, Attr) :-
    (  '$get_from_attr_list'(V, Module, Attr) ->
       false
    ;  true
    ).

'$copy_attr_list'(L, _Module, []) :- var(L), !.
'$copy_attr_list'([Module0:Att|Atts], Module, CopiedAtts) :-
    (  Module0 == Module ->
       CopiedAtts = [Att|CopiedAtts0],
       '$copy_attr_list'(Atts, Module, CopiedAtts0)
    ;  '$copy_attr_list'(Atts, Module, CopiedAtts)
    ).

user:term_expansion(Term0, Terms) :-
    nonvar(Term0),
    Term0 = (:- attribute Atts),
    nonvar(Atts),
    prolog_load_context(module, Module),
    phrase(expand_terms(Atts, Module), Terms).

expand_terms(Atts, Module) -->
    put_attrs_var_check,
    put_attrs(Atts, Module),
    get_attrs_var_check(Module),
    get_attrs(Atts, Module).

put_attrs_var_check -->
    [(put_atts(Var, Attr) :- nonvar(Var),
                             throw(error(uninstantiation_error(Var), put_atts/2))),
     (put_atts(Var, Attr) :- var(Attr),
                             throw(error(instantiation_error, put_atts/2)))].

get_attrs_var_check(Module) -->
    [(get_atts(Var, Attr) :- nonvar(Var),
                             throw(error(uninstantiation_error(Var), get_atts/2))),
     (get_atts(Var, Attr) :- var(Attr),
                             !,
                             '$get_attr_list'(Var, Ls),
                             nonvar(Ls),
			     atts:'$copy_attr_list'(Ls, Module, Attr))].

put_attrs(Name/Arity, Module) -->
    put_attr(Name, Arity, Module),
    [(put_atts(Var, Attr) :- lists:maplist(Module:put_atts(Var), Attr), !)].
put_attrs((Name/Arity, Atts), Module) -->
    { nonvar(Atts) },
    put_attr(Name, Arity, Module),
    put_attrs(Atts, Module).

get_attrs(Name/Arity, Module) -->
    get_attr(Name, Arity, Module).
get_attrs((Name/Arity, Atts), Module) -->
    { nonvar(Atts) },
    get_attr(Name, Arity, Module),
    get_attrs(Atts, Module).

put_attr(Name, Arity, Module) -->
    { functor(Attr, Name, Arity) },
    [(put_atts(V, +Attr) :-
          !,
          '$put_to_attr_list'(V, Module, Attr)),
     (put_atts(V, Attr) :-
          !,
          '$put_to_attr_list'(V, Module, Attr)),
     (put_atts(V, -Attr) :-
          !,
          '$del_from_attr_list'(V, Module, Attr))].

get_attr(Name, Arity, Module) -->
    { functor(Attr, Name, Arity) },
    [(get_atts(V, +Attr) :-
          !,
          functor(Attr, _, _),
          atts:'$get_from_attr_list'(V, Module, Attr)),
     (get_atts(V,  Attr) :-
          !,
          functor(Attr, _, _),
          atts:'$get_from_attr_list'(V, Module, Attr)),
     (get_atts(V, -Attr) :-
          !,
          functor(Attr, _, _),
          atts:'$absent_attr'(V, Module, Attr))].

user:goal_expansion(Term, M:put_atts(Var, Attr)) :-
    nonvar(Term),
    Term = put_atts(Var, M, Attr).
user:goal_expansion(Term, M:get_atts(Var, Attr)) :-
    nonvar(Term),
    Term = get_atts(Var, M, Attr).

term_attributed_variables(Term, Vars) :-
    '$term_attributed_variables'(Term, Vars).
