%%
%% %CopyrightBegin%
%%
%% Copyright Ericsson AB 2012. 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(asn1ct_imm).
-export([per_dec_boolean/0,per_dec_enumerated/2,per_dec_enumerated/3,
per_dec_extension_map/1,
per_dec_integer/2,per_dec_length/3,per_dec_named_integer/3,
per_dec_octet_string/2,per_dec_open_type/1]).
-export([optimize_alignment/1,optimize_alignment/2,
dec_slim_cg/2,dec_code_gen/2]).
-export([effective_constraint/2]).
-import(asn1ct_gen, [emit/1]).
-record(st, {var,
base}).
dec_slim_cg(Imm0, BytesVar) ->
{Imm,_} = optimize_alignment(Imm0),
asn1ct_name:new(v),
[H|T] = atom_to_list(asn1ct_name:curr(v)) ++ "@",
VarBase = [H-($a-$A)|T],
St0 = #st{var=0,base=VarBase},
{Res,Pre,_} = flatten(Imm, BytesVar, St0),
dcg_list_outside(Pre),
Res.
dec_code_gen(Imm, BytesVar) ->
emit(["begin",nl]),
{Dst,DstBuf} = dec_slim_cg(Imm, BytesVar),
emit([",",nl,
"{",Dst,",",DstBuf,"}",nl,
"end"]),
ok.
optimize_alignment(Imm) ->
opt_al(Imm, unknown).
optimize_alignment(Imm, Al) ->
opt_al(Imm, Al).
per_dec_boolean() ->
{map,{get_bits,1,[1]},[{0,false},{1,true}]}.
per_dec_enumerated(NamedList0, Aligned) ->
Constraint = [{'ValueRange',{0,length(NamedList0)-1}}],
NamedList = per_dec_enumerated_fix_list(NamedList0, [enum_error], 0),
Int = per_dec_integer(Constraint, Aligned),
{map,Int,NamedList}.
per_dec_enumerated(BaseNamedList, NamedListExt0, Aligned) ->
Base = per_dec_enumerated(BaseNamedList, Aligned),
NamedListExt = per_dec_enumerated_fix_list(NamedListExt0,
[enum_default], 0),
Ext = {map,per_dec_normally_small_number(Aligned),NamedListExt},
bit_case(Base, Ext).
per_dec_extension_map(Aligned) ->
Len = {add,per_dec_normally_small_number(Aligned),1},
{get_bits,Len,[1,bitstring]}.
per_dec_integer(Constraint0, Aligned) ->
Constraint = effective_constraint(integer, Constraint0),
per_dec_integer_1(Constraint, Aligned).
per_dec_length(SingleValue, _, _Aligned) when is_integer(SingleValue) ->
{value,SingleValue};
per_dec_length({S,S}, _, _Aligned) when is_integer(S) ->
{value,S};
per_dec_length({{_,_}=Constr,_}, AllowZero, Aligned) ->
bit_case(per_dec_length(Constr, AllowZero, Aligned),
per_dec_length(undefined, AllowZero, Aligned));
per_dec_length({Lb,Ub}, _AllowZero, Aligned) when is_integer(Lb),
is_integer(Lb),
Ub =< 65535 ->
per_dec_constrained(Lb, Ub, Aligned);
per_dec_length({_,_}, AllowZero, Aligned) ->
decode_unconstrained_length(AllowZero, Aligned);
per_dec_length(undefined, AllowZero, Aligned) ->
decode_unconstrained_length(AllowZero, Aligned).
per_dec_named_integer(Constraint, NamedList0, Aligned) ->
Int = per_dec_integer(Constraint, Aligned),
NamedList = [{K,V} || {V,K} <- NamedList0] ++ [integer_default],
{map,Int,NamedList}.
per_dec_octet_string(Constraint, Aligned) ->
dec_string(Constraint, 8, Aligned).
per_dec_open_type(Aligned) ->
{get_bits,decode_unconstrained_length(true, Aligned),
[8,binary,{align,Aligned}]}.
%%%
%%% Local functions.
%%%
dec_string(Sv, U, _Aligned) when is_integer(Sv), U*Sv =< 16 ->
{get_bits,Sv,[U,binary]};
dec_string(Sv, U, Aligned) when is_integer(Sv), Sv < 16#10000 ->
{get_bits,Sv,[U,binary,{align,Aligned}]};
dec_string(C, U, Aligned) when is_list(C) ->
dec_string({hd(C),lists:max(C)}, U, Aligned);
dec_string({Sv,Sv}, U, Aligned) ->
dec_string(Sv, U, Aligned);
dec_string({{_,_}=C,_}, U, Aligned) ->
bit_case(dec_string(C, U, Aligned),
dec_string(no, U, Aligned));
dec_string({Lb,Ub}, U, Aligned) when Ub < 16#10000 ->
Len = per_dec_constrained(Lb, Ub, Aligned),
{get_bits,Len,[U,binary,{align,Aligned}]};
dec_string(_, U, Aligned) ->
Al = [{align,Aligned}],
DecRest = fun(V, Buf) ->
asn1ct_func:call(per_common,
decode_fragmented,
[V,Buf,U])
end,
{'case',[{test,{get_bits,1,[1|Al]},0,
{value,{get_bits,
{get_bits,7,[1]},
[U,binary]}}},
{test,{get_bits,1,[1|Al]},1,
{test,{get_bits,1,[1]},0,
{value,{get_bits,
{get_bits,14,[1]},
[U,binary]}}}},
{test,{get_bits,1,[1|Al]},1,
{test,{get_bits,1,[1]},1,
{value,{call,DecRest,{get_bits,6,[1]}}}}}]}.
per_dec_enumerated_fix_list([{V,_}|T], Tail, N) ->
[{N,V}|per_dec_enumerated_fix_list(T, Tail, N+1)];
per_dec_enumerated_fix_list([], Tail, _) -> Tail.
per_dec_integer_1([{'SingleValue',Value}], _Aligned) ->
{value,Value};
per_dec_integer_1([{'ValueRange',{Lb,'MAX'}}], Aligned) when is_integer(Lb) ->
per_dec_unconstrained(Aligned);
per_dec_integer_1([{'ValueRange',{Lb,Ub}}], Aligned) when is_integer(Lb),
is_integer(Ub) ->
per_dec_constrained(Lb, Ub, Aligned);
per_dec_integer_1([{{_,_}=Constr0,_}], Aligned) ->
Constr = effective_constraint(integer, [Constr0]),
bit_case(per_dec_integer(Constr, Aligned),
per_dec_unconstrained(Aligned));
per_dec_integer_1([], Aligned) ->
per_dec_unconstrained(Aligned).
per_dec_unconstrained(Aligned) ->
{get_bits,decode_unconstrained_length(false, Aligned),[8,signed]}.
per_dec_constrained(Lb, Ub, false) ->
Range = Ub - Lb + 1,
Get = {get_bits,uper_num_bits(Range),[1]},
add_lb(Lb, Get);
per_dec_constrained(Lb, Ub, true) ->
Range = Ub - Lb + 1,
Get = if
Range =< 255 ->
{get_bits,per_num_bits(Range),[1,unsigned]};
Range == 256 ->
{get_bits,1,[8,unsigned,{align,true}]};
Range =< 65536 ->
{get_bits,2,[8,unsigned,{align,true}]};
true ->
RangeOctLen = byte_size(binary:encode_unsigned(Range - 1)),
{get_bits,per_dec_length({1,RangeOctLen}, false, true),
[8,unsigned,{align,true}]}
end,
add_lb(Lb, Get).
add_lb(0, Get) -> Get;
add_lb(Lb, Get) -> {add,Get,Lb}.
per_dec_normally_small_number(Aligned) ->
Small = {get_bits,6,[1]},
Unlimited = per_decode_semi_constrained(0, Aligned),
bit_case(Small, Unlimited).
per_decode_semi_constrained(Lb, Aligned) ->
add_lb(Lb, {get_bits,decode_unconstrained_length(false, Aligned),[8]}).
bit_case(Base, Ext) ->
{'case',[{test,{get_bits,1,[1]},0,Base},
{test,{get_bits,1,[1]},1,Ext}]}.
decode_unconstrained_length(AllowZero, Aligned) ->
Al = [{align,Aligned}],
Zero = case AllowZero of
false -> [non_zero];
true -> []
end,
{'case',[{test,{get_bits,1,[1|Al]},0,
{value,{get_bits,7,[1|Zero]}}},
{test,{get_bits,1,[1|Al]},1,
{test,{get_bits,1,[1]},0,
{value,{get_bits,14,[1|Zero]}}}}]}.
uper_num_bits(N) ->
uper_num_bits(N, 1, 0).
uper_num_bits(N, T, B) when N =< T -> B;
uper_num_bits(N, T, B) -> uper_num_bits(N, T bsl 1, B+1).
per_num_bits(2) -> 1;
per_num_bits(N) when N =< 4 -> 2;
per_num_bits(N) when N =< 8 -> 3;
per_num_bits(N) when N =< 16 -> 4;
per_num_bits(N) when N =< 32 -> 5;
per_num_bits(N) when N =< 64 -> 6;
per_num_bits(N) when N =< 128 -> 7;
per_num_bits(N) when N =< 255 -> 8.
%%%
%%% Remove unnecessary aligning to octet boundaries.
%%%
opt_al({get_bits,E0,Opts0}, A0) ->
{E,A1} = opt_al(E0, A0),
Opts = opt_al_1(A1, Opts0),
A = update_al(A1, E, Opts),
{{get_bits,E,Opts},A};
opt_al({call,Fun,E0}, A0) ->
{E,A} = opt_al(E0, A0),
{{call,Fun,E},A};
opt_al({convert,Op,E0}, A0) ->
{E,A} = opt_al(E0, A0),
{{convert,Op,E},A};
opt_al({value,E0}, A0) ->
{E,A} = opt_al(E0, A0),
{{value,E},A};
opt_al({add,E0,I}, A0) when is_integer(I) ->
{E,A} = opt_al(E0, A0),
{{add,E,I},A};
opt_al({test,E0,V,B0}, A0) ->
{E,A1} = opt_al(E0, A0),
{B,A2} = opt_al(B0, A1),
{{test,E,V,B},A2};
opt_al({'case',Cs0}, A0) ->
{Cs,A} = opt_al_cs(Cs0, A0),
{{'case',Cs},A};
opt_al({map,E0,Cs}, A0) ->
{E,A} = opt_al(E0, A0),
{{map,E,Cs},A};
opt_al(I, A) when is_integer(I) ->
{I,A}.
opt_al_cs([C0|Cs0], A0) ->
{C,A1} = opt_al(C0, A0),
{Cs,A2} = opt_al_cs(Cs0, A0),
{[C|Cs],merge_al(A1, A2)};
opt_al_cs([], _) -> {[],none}.
merge_al(unknown, _) -> unknown;
merge_al(Other, none) -> Other;
merge_al(_, unknown) -> unknown;
merge_al(I0, I1) ->
case {I0 rem 8,I1 rem 8} of
{I,I} -> I;
{_,_} -> unknown
end.
opt_al_1(unknown, Opts) ->
Opts;
opt_al_1(A, Opts0) ->
case alignment(Opts0) of
none ->
Opts0;
full ->
case A rem 8 of
0 ->
%% Already in alignment.
proplists:delete(align, Opts0);
Bits ->
%% Cheaper alignment with a constant padding.
Opts1 = proplists:delete(align, Opts0),
[{align,8-Bits }|Opts1]
end;
A -> %Assertion.
Opts0
end.
update_al(A0, E, Opts) ->
A = case alignment(Opts) of
none -> A0;
full -> 0;
Bits when is_integer(A0) ->
0 = (A0 + Bits) rem 8; %Assertion.
_ ->
0
end,
[U] = [U || U <- Opts, is_integer(U)],
if
U rem 8 =:= 0 -> A;
is_integer(A), is_integer(E) -> A + U*E;
true -> unknown
end.
%%%
%%% Flatten the intermediate format and assign temporaries.
%%%
flatten({get_bits,I,U}, Buf0, St0) when is_integer(I) ->
{Dst,St} = new_var_pair(St0),
Gb = {get_bits,{I,Buf0},U,Dst},
flatten_align(Gb, [], St);
flatten({get_bits,E0,U}, Buf0, St0) ->
{E,Pre,St1} = flatten(E0, Buf0, St0),
{Dst,St2} = new_var_pair(St1),
Gb = {get_bits,E,U,Dst},
flatten_align(Gb, Pre, St2);
flatten({test,{get_bits,I,U},V,E0}, Buf0, St0) when is_integer(I) ->
{DstBuf0,St1} = new_var("Buf", St0),
Gb = {get_bits,{I,Buf0},U,{V,DstBuf0}},
{{_Dst,DstBuf},Pre0,St2} = flatten_align(Gb, [], St1),
{E,Pre1,St3} = flatten(E0, DstBuf, St2),
{E,Pre0++Pre1,St3};
flatten({add,E0,I}, Buf0, St0) ->
{{Src,Buf},Pre,St1} = flatten(E0, Buf0, St0),
{Dst,St} = new_var("Add", St1),
{{Dst,Buf},Pre++[{add,Src,I,Dst}],St};
flatten({'case',Cs0}, Buf0, St0) ->
{Dst,St1} = new_var_pair(St0),
{Cs1,St} = flatten_cs(Cs0, Buf0, St1),
{Al,Cs2} = flatten_hoist_align(Cs1),
{Dst,Al++[{'case',Buf0,Cs2,Dst}],St};
flatten({map,E0,Cs0}, Buf0, St0) ->
{{E,DstBuf},Pre,St1} = flatten(E0, Buf0, St0),
{Dst,St2} = new_var("Int", St1),
Cs = flatten_map_cs(Cs0, E),
{{Dst,DstBuf},Pre++[{'map',E,Cs,{Dst,DstBuf}}],St2};
flatten({value,V0}, Buf0, St0) when is_integer(V0) ->
{{V0,Buf0},[],St0};
flatten({value,V0}, Buf0, St0) ->
flatten(V0, Buf0, St0);
flatten({convert,Op,E0}, Buf0, St0) ->
{{E,Buf},Pre,St1} = flatten(E0, Buf0, St0),
{Dst,St2} = new_var("Conv", St1),
{{Dst,Buf},Pre++[{convert,Op,E,Dst}],St2};
flatten({call,Fun,E0}, Buf0, St0) ->
{Src,Pre,St1} = flatten(E0, Buf0, St0),
{Dst,St2} = new_var_pair(St1),
{Dst,Pre++[{call,Fun,Src,Dst}],St2}.
flatten_cs([C0|Cs0], Buf, St0) ->
{C,Pre,St1} = flatten(C0, Buf, St0),
{Cs,St2} = flatten_cs(Cs0, Buf, St0),
St3 = St2#st{var=max(St1#st.var, St2#st.var)},
{[Pre++[{return,C}]|Cs],St3};
flatten_cs([], _, St) -> {[],St}.
flatten_map_cs(Cs, Var) ->
flatten_map_cs_1(Cs, {Var,Cs}).
flatten_map_cs_1([{K,V}|Cs], DefData) ->
[{{asis,K},{asis,V}}|flatten_map_cs_1(Cs, DefData)];
flatten_map_cs_1([integer_default], {Int,_}) ->
[{'_',Int}];
flatten_map_cs_1([enum_default], {Int,_}) ->
[{'_',["{asn1_enum,",Int,"}"]}];
flatten_map_cs_1([enum_error], {Var,Cs}) ->
Vs = [V || {_,V} <- Cs],
[{'_',["exit({error,{asn1,{decode_enumerated,{",Var,",",
{asis,Vs},"}}}})"]}];
flatten_map_cs_1([], _) -> [].
flatten_hoist_align([[{align_bits,_,_}=Ab|T]|Cs]) ->
flatten_hoist_align_1(Cs, Ab, [T]);
flatten_hoist_align(Cs) -> {[],Cs}.
flatten_hoist_align_1([[Ab|T]|Cs], Ab, Acc) ->
flatten_hoist_align_1(Cs, Ab, [T|Acc]);
flatten_hoist_align_1([], Ab, Acc) ->
{[Ab],lists:reverse(Acc)}.
flatten_align({get_bits,{SrcBits,SrcBuf},U,Dst}=Gb0, Pre, St0) ->
case alignment(U) of
none ->
flatten_align_1(U, Dst, Pre++[Gb0], St0);
full ->
{PadBits,St1} = new_var("Pad", St0),
{DstBuf,St2} = new_var("Buf", St1),
Ab = {align_bits,SrcBuf,PadBits},
Agb = {get_bits,{PadBits,SrcBuf},[1],{'_',DstBuf}},
Gb = {get_bits,{SrcBits,DstBuf},U,Dst},
flatten_align_1(U, Dst, Pre++[Ab,Agb,Gb], St2);
PadBits when is_integer(PadBits), PadBits > 0 ->
{DstBuf,St1} = new_var("Buf", St0),
Agb = {get_bits,{PadBits,SrcBuf},[1],{'_',DstBuf}},
Gb = {get_bits,{SrcBits,DstBuf},U,Dst},
flatten_align_1(U, Dst, Pre++[Agb,Gb], St1)
end.
flatten_align_1(U, {D,_}=Dst, Pre, St) ->
case is_non_zero(U) of
false ->
{Dst,Pre,St};
true ->
{Dst,Pre++[{non_zero,D}],St}
end.
new_var_pair(St0) ->
{Var,St1} = new_var("V", St0),
{Buf,St2} = new_var("Buf", St1),
{{Var,Buf},St2}.
new_var(Tag, #st{base=VarBase,var=N}=St) ->
{VarBase++Tag++integer_to_list(N),St#st{var=N+1}}.
alignment([{align,false}|_]) -> none;
alignment([{align,true}|_]) -> full;
alignment([{align,Bits}|_]) -> Bits;
alignment([_|T]) -> alignment(T);
alignment([]) -> none.
is_non_zero(Fl) ->
lists:member(non_zero, Fl).
%%%
%%% Generate Erlang code from the flattened intermediate format.
%%%
dcg_list_outside([{align_bits,Buf,SzVar}|T]) ->
emit([SzVar," = bit_size(",Buf,") band 7"]),
iter_dcg_list_outside(T);
dcg_list_outside([{'case',Buf,Cs,Dst}|T]) ->
dcg_case(Buf, Cs, Dst),
iter_dcg_list_outside(T);
dcg_list_outside([{'map',Val,Cs,Dst}|T]) ->
dcg_map(Val, Cs, Dst),
iter_dcg_list_outside(T);
dcg_list_outside([{add,S1,S2,Dst}|T]) ->
emit([Dst," = ",S1," + ",S2]),
iter_dcg_list_outside(T);
dcg_list_outside([{return,{V,Buf}}|T]) ->
emit(["{",V,",",Buf,"}"]),
iter_dcg_list_outside(T);
dcg_list_outside([{call,Fun,{V,Buf},{Dst,DstBuf}}|T]) ->
emit(["{",Dst,",",DstBuf,"} = "]),
Fun(V, Buf),
iter_dcg_list_outside(T);
dcg_list_outside([{convert,Op,V,Dst}|T]) ->
emit([Dst," = ",Op,"(",V,")"]),
iter_dcg_list_outside(T);
dcg_list_outside([{get_bits,{_,Buf0},_,_}|_]=L0) ->
emit("<<"),
{L,Buf} = dcg_list_inside(L0, buf),
emit([Buf,"/bitstring>> = ",Buf0]),
iter_dcg_list_outside(L);
dcg_list_outside([]) ->
emit("ignore"),
ok.
iter_dcg_list_outside([_|_]=T) ->
emit([",",nl]),
dcg_list_outside(T);
iter_dcg_list_outside([]) -> ok.
dcg_case(Buf, Cs, {Dst,DstBuf}) ->
emit(["{",Dst,",",DstBuf,"} = case ",Buf," of",nl]),
dcg_case_cs(Cs),
emit("end").
dcg_case_cs([C|Cs]) ->
emit("<<"),
{T0,DstBuf} = dcg_list_inside(C, buf),
emit([DstBuf,"/bitstring>>"]),
T1 = dcg_guard(T0),
dcg_list_outside(T1),
case Cs of
[] -> emit([nl]);
[_|_] -> emit([";",nl])
end,
dcg_case_cs(Cs);
dcg_case_cs([]) -> ok.
dcg_guard([{non_zero,Src}|T]) ->
emit([" when ",Src," =/= 0 ->",nl]),
T;
dcg_guard(T) ->
emit([" ->",nl]),
T.
dcg_map(Val, Cs, {Dst,_}) ->
emit([Dst," = case ",Val," of",nl]),
dcg_map_cs(Cs),
emit("end").
dcg_map_cs([{K,V}]) ->
emit([K," -> ",V,nl]);
dcg_map_cs([{K,V}|Cs]) ->
emit([K," -> ",V,";",nl]),
dcg_map_cs(Cs).
dcg_list_inside([{get_bits,{Sz,_},Fl0,{Dst,DstBuf}}|T], _) ->
Fl = bit_flags(Fl0, []),
emit([mk_dest(Dst),":",Sz,Fl,","]),
dcg_list_inside(T, DstBuf);
dcg_list_inside(L, Dst) -> {L,Dst}.
bit_flags([1|T], Acc) ->
bit_flags(T, Acc);
bit_flags([{align,_}|T], Acc) ->
bit_flags(T, Acc);
bit_flags([non_zero|T], Acc) ->
bit_flags(T, Acc);
bit_flags([U|T], Acc) when is_integer(U) ->
bit_flags(T, ["unit:"++integer_to_list(U)|Acc]);
bit_flags([H|T], Acc) ->
bit_flags(T, [atom_to_list(H)|Acc]);
bit_flags([], []) ->
"";
bit_flags([], Acc) ->
"/" ++ bit_flags_1(Acc, "").
bit_flags_1([H|T], Sep) ->
Sep ++ H ++ bit_flags_1(T, "-");
bit_flags_1([], _) -> [].
mk_dest(I) when is_integer(I) ->
integer_to_list(I);
mk_dest(S) -> S.
%% effective_constraint(Type,C)
%% Type = atom()
%% C = [C1,...]
%% C1 = {'SingleValue',SV} | {'ValueRange',VR} | {atom(),term()}
%% SV = integer() | [integer(),...]
%% VR = {Lb,Ub}
%% Lb = 'MIN' | integer()
%% Ub = 'MAX' | integer()
%% Returns a single value if C only has a single value constraint, and no
%% value range constraints, that constrains to a single value, otherwise
%% returns a value range that has the lower bound set to the lowest value
%% of all single values and lower bound values in C and the upper bound to
%% the greatest value.
effective_constraint(integer,[C={{_,_},_}|_Rest]) -> % extension
[C];
effective_constraint(integer, C) ->
SVs = get_constraints(C, 'SingleValue'),
SV = effective_constr('SingleValue', SVs),
VRs = get_constraints(C, 'ValueRange'),
VR = effective_constr('ValueRange', VRs),
greatest_common_range(SV, VR);
effective_constraint(bitstring, C) ->
get_constraint(C, 'SizeConstraint').
effective_constr(_, []) -> [];
effective_constr('SingleValue', List) ->
SVList = lists:flatten(lists:map(fun(X) -> element(2, X) end, List)),
%% Sort and remove duplicates before generating SingleValue or ValueRange
%% In case of ValueRange, also check for 'MIN and 'MAX'
case lists:usort(SVList) of
[N] ->
[{'SingleValue',N}];
[_|_]=L ->
[{'ValueRange',{least_Lb(L),greatest_Ub(L)}}]
end;
effective_constr('ValueRange', List) ->
LBs = lists:map(fun({_,{Lb,_}}) -> Lb end, List),
UBs = lists:map(fun({_,{_,Ub}}) -> Ub end, List),
Lb = least_Lb(LBs),
[{'ValueRange',{Lb,lists:max(UBs)}}].
greatest_common_range([], VR) ->
VR;
greatest_common_range(SV, []) ->
SV;
greatest_common_range([{_,Int}], [{_,{'MIN',Ub}}])
when is_integer(Int), Int > Ub ->
[{'ValueRange',{'MIN',Int}}];
greatest_common_range([{_,Int}],[{_,{Lb,Ub}}])
when is_integer(Int), Int < Lb ->
[{'ValueRange',{Int,Ub}}];
greatest_common_range([{_,Int}],VR=[{_,{_Lb,_Ub}}]) when is_integer(Int) ->
VR;
greatest_common_range([{_,L}],[{_,{Lb,Ub}}]) when is_list(L) ->
Min = least_Lb([Lb|L]),
Max = greatest_Ub([Ub|L]),
[{'ValueRange',{Min,Max}}];
greatest_common_range([{_,{Lb1,Ub1}}], [{_,{Lb2,Ub2}}]) ->
Min = least_Lb([Lb1,Lb2]),
Max = greatest_Ub([Ub1,Ub2]),
[{'ValueRange',{Min,Max}}].
least_Lb(L) ->
case lists:member('MIN', L) of
true -> 'MIN';
false -> lists:min(L)
end.
greatest_Ub(L) ->
case lists:member('MAX', L) of
true -> 'MAX';
false -> lists:max(L)
end.
get_constraint(C, Key) ->
case lists:keyfind(Key, 1, C) of
false -> no;
{_,V} -> V
end.
get_constraints([{Key,_}=Pair|T], Key) ->
[Pair|get_constraints(T, Key)];
get_constraints([_|T], Key) ->
get_constraints(T, Key);
get_constraints([], _) -> [].