%%
%% %CopyrightBegin%
%%
%% Copyright Ericsson AB 2000-2015. All Rights Reserved.
%%
%% 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.
%%
%% %CopyrightEnd%
%%
-module(xref_compiler).
-include("xref.hrl").
%-define(debug, true).
-ifdef(debug).
-define(FORMAT(P, A), io:format(P, A)).
-define(CALL(F), F).
-else.
-define(FORMAT(P, A), ok).
-define(CALL(F), ok).
-endif.
%% Avoid warning for local function error/1 clashing with autoimported BIF.
-compile({no_auto_import,[error/1]}).
-export([compile/2]).
-export([update_graph_counter/3]).
-export([format_error/1]).
-import(lists,
[concat/1, foldl/3, nthtail/2, reverse/1, sort/1, sublist/2]).
-import(sofs,
[composite/2, difference/2, empty_set/0, from_term/1,
intersection/2, is_empty_set/1, multiple_relative_product/2,
projection/2, relation/1, relation_to_family/1,
restriction/2, specification/2, substitution/2,
to_external/1, union/2, union_of_family/1]).
%%
%% Exported functions
%%
compile(Chars, Table) ->
case xref_scanner:scan(Chars) of
{ok, Tokens} ->
case xref_parser:parse(Tokens) of
{ok, ParseTree} ->
?FORMAT("ParseTree ~p~n", [ParseTree]),
case catch statements(ParseTree, Table) of
E={error, _, _} ->
E;
{ok, UV, P} ->
%% User variables to be.
Table1 = user_vars(UV, Table),
?CALL(statistics(runtime)),
Reply = i(P, Table1),
?CALL({_, Time} = statistics(runtime)),
?FORMAT("Result in ~p ms~n",[Time]),
Reply
end;
{error, {Line, _Module, Error}} ->
error({parse_error, Line, Error})
end;
{error, Info, Line} ->
error({parse_error, Line, Info})
end.
format_error({error, Module, Error}) ->
Module:format_error(Error);
format_error({parse_error, Line, Error}) ->
format_parse_error(Error, format_line(Line));
format_error({variable_reassigned, Expr}) ->
io_lib:format("Variable assigned more than once: ~ts~n", [Expr]);
format_error({unknown_variable, Name}) ->
io_lib:format("Variable ~tp used before set~n", [Name]);
format_error({type_error, Expr}) ->
io_lib:format("Operator applied to argument(s) of different or "
"invalid type(s): ~ts~n", [Expr]);
format_error({type_mismatch, Expr1, Expr2}) ->
io_lib:format("Constants of different types: ~ts, ~ts~n",
[Expr1, Expr2]);
format_error({unknown_constant, Constant}) ->
io_lib:format("Unknown constant ~ts~n", [Constant]);
format_error(E) ->
io_lib:format("~tp~n", [E]).
%%
%% Local functions
%%
user_vars([{{user,Name}, Val} | UV], Table) ->
user_vars(UV, dict:store(Name, Val, Table));
user_vars([_V | UV], Table) ->
user_vars(UV, Table);
user_vars([], Table) ->
Table.
statements(Stmts, Table) ->
statements(Stmts, Table, [], []).
statements([Stmt={assign, VarType, Name, E} | Stmts0], Table, L, UV) ->
case dict:find(Name, Table) of
{ok, _} ->
throw_error({variable_reassigned, xref_parser:t2s(Stmt)});
error ->
{Type, OType, NewE} = t_expr(E, Table),
Val = #xref_var{name = Name, vtype = VarType,
otype = OType, type = Type},
NewTable = dict:store(Name, Val, Table),
Stmts = if Stmts0 =:= [] -> [{variable, Name}]; true -> Stmts0 end,
Variable = {VarType, Name},
Put = {put, Variable, NewE},
statements(Stmts, NewTable, [Put | L], [{Variable,Val} | UV])
end;
statements([Expr], Table, L, UV) ->
{Type, OType, NewE} = t_expr(Expr, Table),
E1 = un_familiarize(Type, OType, NewE),
NE = case {Type, OType} of
%% Edges with empty sets of line numbers are removed.
{{line, _}, edge} ->
{relation_to_family, E1};
{_Type, edge_closure} ->
%% Fake a closure usage, just to make sure it is destroyed.
E2 = {fun graph_access/2, E1, E1},
{fun(_E) -> 'closure()' end, E2};
_Else -> E1
end,
{ok, UV, stats(L, NE)}.
stats([{put, V, X} | Ss], E) ->
stats(Ss, {put, V, X, E});
stats([], E) ->
E.
t_expr(E, Table) ->
{expr, Type, OType, E1} = check_expr(E, Table),
?FORMAT("TExpr:~n~p~n",[E1]),
E2 = convert(E1),
?FORMAT("After conversion:~n~p~n",[E2]),
{Type, OType, E2}.
%%% check_expr/2 translates Expr in xref_parser.yrl into TExpr:
%%%
%%% TExpr = {expr, Type, ObjectType, Expr}
%%% Expr = {constants, [Constant]}
%%% | {variable, {VarType, VarName}}
%%% | {call, Call, Expr}
%%% | {call, Call, Expr, Expr}
%%% | {call, restriction, integer(), Expr, Expr}
%%% | {convert, ObjectType, Type, Type}
%%% | {convert, Type, Type}
%%% Constant = atom() | {atom(), atom()} | MFA | {MFA, MFA}
%%% Call = atom() % function in the sofs module
%%% | fun()
%%% Type = {line, LineType} | function | module | application | release
%%% | number
%%% LineType = line | local_call | external_call | export_call | all_line_call
%%% VarType = predef | user | tmp
%%% ObjectType = vertex | vertex_set | edge | edge_set | edge_closure | path
%%% | number
%%% MFA = {atom(), atom(), integer()}
%% -> TExpr
check_expr({list, L}, Table) ->
check_constants(L, Table);
check_expr({tuple, L}, Table) ->
{expr, Type, vertex, _Consts} = check_constants(L, Table),
Cs = reverse(constant_vertices(L, [])),
{expr, Type, path, {constants, Cs}};
check_expr({variable, Name}, Table) ->
case dict:find(Name, Table) of
{ok, #xref_var{vtype = VarType, otype = OType, type = Type}} ->
V0 = {variable, {VarType, Name}},
V = case {VarType, Type, OType} of
{predef, release, _} -> V0;
{predef, application, _} -> V0;
{predef, module, _} -> V0;
{predef, function, vertex} -> V0;
{predef, function, edge} -> {call, union_of_family, V0};
_Else -> V0
end,
{expr, Type, OType, V};
error ->
throw_error({unknown_variable, Name})
end;
check_expr({type, {type, _Type}, E}, Table) ->
check_expr(E, Table);
check_expr(Expr={type, {convert, NewType0}, E}, Table) ->
NewType = what_type(NewType0),
{expr, OldType, OType, NE} = check_expr(E, Table),
ok = check_conversion(OType, OldType, NewType, Expr),
{expr, NewType, OType, {convert, OType, OldType, NewType, NE}};
check_expr(Expr={set, SOp, E}, Table) ->
{expr, Type, OType0, E1} = check_expr(E, Table),
OType = case {OType0, SOp} of
{edge, range} -> vertex;
{edge, domain} -> vertex;
{edge, weak} -> edge;
{edge, strict} -> edge;
{edge_set, range} -> vertex_set;
{edge_set, domain} -> vertex_set;
{edge_set, weak} -> edge_set;
{edge_set, strict} -> edge_set;
_ ->
throw_error({type_error, xref_parser:t2s(Expr)})
end,
Op = set_op(SOp),
NE = function_vertices_to_family(Type, OType, {call, Op, E1}),
{expr, Type, OType, NE};
check_expr(Expr={graph, Op, E}, Table) ->
{expr, Type, NOType, E1} = check_expr(E, Table),
case Type of
{line, _LineType} ->
throw_error({type_error, xref_parser:t2s(Expr)});
_Else ->
ok
end,
OType =
case {NOType, Op} of
{edge, components} -> vertex_set;
{edge, condensation} -> edge_set;
{edge, closure} -> edge_closure;
{edge_closure, components} -> vertex_set;
{edge_closure, condensation} -> edge_set;
{edge_closure, closure} -> edge_closure;
%% Neither need nor want these ones:
%% {edge_set, closure} -> edge_set_closure;
%% {edge_set, components} -> vertex_set_set;
_ ->
throw_error({type_error, xref_parser:t2s(Expr)})
end,
E2 = {convert, NOType, edge_closure, E1},
NE = case Op of
closure -> E2;
_Op -> use_of_closure(Op, E2)
end,
{expr, Type, OType, NE};
check_expr(Expr={numeric, '#', E}, Table) ->
{expr, Type, OType, E1} = check_expr(E, Table),
case OType of
vertex -> ok;
vertex_set -> ok;
edge -> ok;
edge_set -> ok;
_Else -> throw_error({type_error, xref_parser:t2s(Expr)})
end,
NE = {convert, OType, Type, number, E1},
{expr, number, number, {call, no_elements, NE}};
check_expr(Expr={set, SOp, E1, E2}, Table) ->
%% sets and numbers...
{expr, Type1, OType1, NE1} = check_expr(E1, Table),
{expr, Type2, OType2, NE2} = check_expr(E2, Table),
OType = case {OType1, OType2} of
{vertex, vertex} -> vertex;
{edge, edge} -> edge;
{number, number} -> number;
_ -> throw_error({type_error, xref_parser:t2s(Expr)})
end,
case OType of
number ->
{expr, number, number, {call, ari_op(SOp), NE1, NE2}};
_Else -> % set
{Type, NewE1, NewE2} =
case {type_ord(Type1), type_ord(Type2)} of
{T1, T2} when T1 =:= T2 ->
%% Example: if Type1 = {line, line} and
%% Type2 = {line, export_line}, then this is not
%% correct, but works:
{Type1, NE1, NE2};
{T1, T2} when T1 < 2; T2 < 2 ->
throw_error({type_error, xref_parser:t2s(Expr)});
{T1, T2} when T1 > T2 ->
{Type2, {convert, OType, Type1, Type2, NE1}, NE2};
{T1, T2} when T1 < T2 ->
{Type1, NE1, {convert, OType, Type2, Type1, NE2}}
end,
Op = set_op(SOp, Type, OType),
{expr, Type, OType, {call, Op, NewE1, NewE2}}
end;
check_expr(Expr={restr, ROp, E1, E2}, Table) ->
{expr, Type1, OType1, NE1} = check_expr(E1, Table),
{expr, Type2, OType2, NE2} = check_expr(E2, Table),
case {Type1, Type2} of
{{line, _LineType1}, _Type2} ->
throw_error({type_error, xref_parser:t2s(Expr)});
{_Type1, {line, _LineType2}} ->
throw_error({type_error, xref_parser:t2s(Expr)});
_ ->
ok
end,
case {OType1, OType2} of
{edge, vertex} when ROp =:= '|||' ->
{expr, _, _, R1} = restriction('|', E1, Type1, NE1, Type2, NE2),
{expr, _, _, R2} = restriction('||', E1, Type1, NE1, Type2, NE2),
{expr, Type1, edge, {call, intersection, R1, R2}};
{edge, vertex} ->
restriction(ROp, E1, Type1, NE1, Type2, NE2);
{edge_closure, vertex} when ROp =:= '|||' ->
{expr, _, _, R1} =
closure_restriction('|', Type1, Type2, OType2, NE1, NE2),
{expr, _, _, R2} =
closure_restriction('||', Type1, Type2, OType2, NE1, NE2),
{expr, Type1, edge, {call, intersection, R1, R2}};
{edge_closure, vertex} ->
closure_restriction(ROp, Type1, Type2, OType2, NE1, NE2);
_ ->
throw_error({type_error, xref_parser:t2s(Expr)})
end;
check_expr(Expr={path, E1, E2}, Table) ->
{expr, Type1, OType1a, E1a} = check_expr(E1, Table),
{expr, Type2, OType2, E2a} = check_expr(E2, Table),
case {Type1, Type2} of
{{line, _LineType1}, _Type2} ->
throw_error({type_error, xref_parser:t2s(Expr)});
{_Type1, {line, _LineType2}} ->
throw_error({type_error, xref_parser:t2s(Expr)});
_Else ->
ok
end,
E2b = {convert, OType2, Type2, Type1, E2a},
{OType1, NE1} = path_arg(OType1a, E1a),
NE2 = case {OType1, OType2} of
{path, edge} -> {convert, OType2, edge_closure, E2b};
{path, edge_closure} when Type1 =:= Type2 -> E2b;
_ -> throw_error({type_error, xref_parser:t2s(Expr)})
end,
{expr, Type1, path, use_of_closure(path, NE2, NE1)};
check_expr({regexpr, RExpr, Type0}, _Table) ->
%% Using the "universal" variables is not optimal as regards speed,
%% but it is simple...
Type = what_type(Type0),
V = case Type of
function -> v;
module -> 'M';
application -> 'A';
release -> 'R'
end,
Var = {variable, {predef, V}},
Call = {call, fun(E, V2) -> xref_utils:regexpr(E, V2) end,
{constants, RExpr}, Var},
{expr, Type, vertex, Call};
check_expr(C={constant, _Type, _OType, _C}, Table) ->
check_constants([C], Table).
path_arg(edge, E={constants, C}) ->
case to_external(C) of
[{V1,V2}] -> {path, {constants, [V1, V2]}};
_ -> {edge, E}
end;
path_arg(OType, E) ->
{OType, E}.
check_conversion(OType, Type1, Type2, Expr) ->
case conversions(OType, Type1, Type2) of
ok -> ok;
not_ok -> throw_error({type_error, xref_parser:t2s(Expr)})
end.
%% Allowed conversions.
conversions(_OType, {line, LineType}, {line, LineType}) -> ok;
conversions(edge, {line, _}, {line, all_line_call}) -> ok;
conversions(edge, From, {line, Line})
when is_atom(From), Line =/= all_line_call -> ok;
conversions(vertex, From, {line, line}) when is_atom(From) -> ok;
conversions(vertex, From, To) when is_atom(From), is_atom(To) -> ok;
conversions(edge, From, To) when is_atom(From), is_atom(To) -> ok;
%% "Extra":
conversions(edge, {line, Line}, To)
when is_atom(To), Line =/= all_line_call -> ok;
conversions(vertex, {line, line}, To) when is_atom(To) -> ok;
conversions(_OType, _From, _To) -> not_ok.
set_op(union, {line, _LineType}, edge) -> family_union;
set_op(intersection, {line, _LineType}, edge) -> family_intersection;
set_op(difference, {line, _LineType}, edge) -> family_difference;
set_op(union, function, vertex) -> family_union;
set_op(intersection, function, vertex) -> family_intersection;
set_op(difference, function, vertex) -> family_difference;
set_op(SOp, _Type, _OType) -> SOp.
set_op(weak) -> weak_relation;
set_op(strict) -> strict_relation;
set_op(Op) -> Op.
ari_op(union) -> fun(X, Y) -> X + Y end;
ari_op(intersection) -> fun(X, Y) -> X * Y end;
ari_op(difference) -> fun(X, Y) -> X - Y end.
restriction(ROp, E1, Type1, NE1, Type2, NE2) ->
{Column, _} = restr_op(ROp),
case NE1 of
{call, union_of_family, _E} when ROp =:= '|' ->
restriction(Column, Type1, E1, Type2, NE2);
{call, union_of_family, _E} when ROp =:= '||' ->
E1p = {inverse, E1},
restriction(Column, Type1, E1p, Type2, NE2);
_ ->
NE2a = {convert, vertex, Type2, Type1, NE2},
NE2b = family_to_function_vertices(Type1, vertex, NE2a),
{expr, Type1, edge, {call, restriction, Column, NE1, NE2b}}
end.
restriction(Column, Type1, VE, Type2, E2) when Type1 =:= function ->
M = {convert, vertex, Type2, module, E2},
Restr = {call, union_of_family, {call, restriction, VE, M}},
C = {convert, vertex, Type2, Type1, E2},
F = family_to_function_vertices(Type1, vertex, C),
{expr, Type1, edge, {call, restriction, Column, Restr, F}}.
closure_restriction(Op, Type1, Type2, OType2, E1, E2) ->
{_, Fun} = restr_op(Op),
E2a = {convert, OType2, Type2, Type1, E2},
E2b = family_to_function_vertices(Type1, vertex, E2a),
{expr, Type1, edge, use_of_closure(Fun, E1, E2b)}.
restr_op('|') -> {1, call};
restr_op('||') -> {2, use}.
%% Closures (digraphs) must be deleted, but not too soon. A wrapper
%% is inserted here for every use of a closure, to make sure that a
%% 'save' and an 'unput' instruction are inserted for every digraph, in
%% particular the temporary ones. The 'unput' instruction must occur
%% _after_ the call to the function that uses the digraph (the default
%% is that it is inserted _before_ the call).
use_of_closure(Op, C) ->
access_of_closure(C, {call, fun(X) -> xref_utils:Op(X) end, C}).
use_of_closure(Op, C, E) ->
access_of_closure(C, {call, fun(X, Y) -> xref_utils:Op(X, Y) end, C, E}).
access_of_closure(C, E) ->
{call, fun graph_access/2, C, E}.
check_constants(Cs=[C={constant, Type0, OType, _Con} | Cs1], Table) ->
check_mix(Cs1, Type0, OType, C),
Types = case Type0 of
unknown -> ['Rel', 'App', 'Mod'];
T -> [T]
end,
case split(Types, Cs, Table) of
[{TypeToBe, _Cs}] ->
S = from_term([Con || {constant, _T, _OT, Con} <- Cs]),
Type = what_type(TypeToBe),
E = function_vertices_to_family(Type, OType, {constants, S}),
{expr, Type, OType, E};
[{Type1, [C1|_]}, {Type2, [C2|_]} | _] ->
throw_error({type_mismatch,
make_vertex(Type1, C1),
make_vertex(Type2, C2)})
end.
check_mix([C={constant, 'Fun', OType, _Con} | Cs], 'Fun', OType, _C0) ->
check_mix(Cs, 'Fun', OType, C);
check_mix([C={constant, Type, OType, _Con} | Cs], Type0, OType, _C0)
when Type =/= 'Fun', Type0 =/= 'Fun' ->
check_mix(Cs, Type, OType, C);
check_mix([C | _], _Type0, _OType0, C0) ->
throw_error({type_mismatch, xref_parser:t2s(C0), xref_parser:t2s(C)});
check_mix([], _Type0, _OType0, _C0) ->
ok.
split(Types, Cs, Table) ->
Vs = from_term(constant_vertices(Cs, [])),
split(Types, Vs, empty_set(), unknown, Table, []).
split([Type | Types], Vs, AllSoFar, _Type, Table, L) ->
S0 = known_vertices(Type, Vs, Table),
S = difference(S0, AllSoFar),
case is_empty_set(S) of
true ->
split(Types, Vs, AllSoFar, Type, Table, L);
false ->
All = union(AllSoFar, S0),
split(Types, Vs, All, Type, Table,
[{Type, to_external(S)} | L])
end;
split([], Vs, All, Type, _Table, L) ->
case to_external(difference(Vs, All)) of
[] -> L;
[C|_] -> throw_error({unknown_constant, make_vertex(Type, C)})
end.
make_vertex(Type, C) ->
xref_parser:t2s({constant, Type, vertex, C}).
constant_vertices([{constant, _Type, edge, {A,B}} | Cs], L) ->
constant_vertices(Cs, [A, B | L]);
constant_vertices([{constant, _Type, vertex, V} | Cs], L) ->
constant_vertices(Cs, [V | L]);
constant_vertices([], L) ->
L.
known_vertices('Fun', Cs, T) ->
M = projection(1, Cs),
F = union_of_family(restriction(fetch_value(v, T), M)),
union(bifs(Cs), intersection(Cs, F));
known_vertices('Mod', Cs, T) ->
intersection(Cs, fetch_value('M', T));
known_vertices('App', Cs, T) ->
intersection(Cs, fetch_value('A', T));
known_vertices('Rel', Cs, T) ->
intersection(Cs, fetch_value('R', T)).
bifs(Cs) ->
specification({external,
fun({M,F,A}) -> xref_utils:is_builtin(M, F, A) end},
Cs).
function_vertices_to_family(function, vertex, E) ->
{call, partition_family, 1, E};
function_vertices_to_family(_Type, _OType, E) ->
E.
family_to_function_vertices(function, vertex, E) ->
{call, union_of_family, E};
family_to_function_vertices(_Type, _OType, E) ->
E.
-define(Q(E), {quote, E}).
convert({inverse, {variable, Variable}}) ->
{get, {inverse, var_name(Variable)}};
convert({variable, Variable}) ->
{get, var_name(Variable)};
convert({convert, FromOType, ToOType, E}) ->
convert(convert(E), FromOType, ToOType);
convert({convert, OType, FromType, ToType, E}) ->
convert(convert(E), OType, FromType, ToType);
convert({call, Op, E}) ->
{Op, convert(E)};
convert({call, Op, E1, E2}) ->
{Op, convert(E1), convert(E2)};
convert({call, Op, E1, E2, E3}) ->
{Op, convert(E1), convert(E2), convert(E3)};
convert({constants, Constants}) ->
?Q(Constants);
convert(I) when is_integer(I) ->
?Q(I).
var_name({predef, VarName}) -> VarName;
var_name(Variable) -> Variable.
convert(E, OType, OType) ->
E;
convert(E, edge, edge_closure) ->
{fun(S) -> xref_utils:closure(S) end, E}.
convert(E, OType, FromType, number) ->
un_familiarize(FromType, OType, E);
convert(E, OType, FromType, ToType) ->
case {type_ord(FromType), type_ord(ToType)} of
{FT, To} when FT =:= To ->
E;
{FT, ToT} when FT > ToT ->
special(OType, FromType, ToType, E);
{FT, ToT} when FT < ToT ->
general(OType, FromType, ToType, E)
end.
-define(T(V), {tmp, V}).
general(_ObjectType, FromType, ToType, X) when FromType =:= ToType ->
X;
general(edge, {line, _LineType}, ToType, LEs) ->
VEs = {projection, ?Q({external, fun({V1V2,_Ls}) -> V1V2 end}), LEs},
general(edge, function, ToType, VEs);
general(edge, function, ToType, VEs) ->
MEs = {projection,
?Q({external, fun({{M1,_,_},{M2,_,_}}) -> {M1,M2} end}),
VEs},
general(edge, module, ToType, MEs);
general(edge, module, ToType, MEs) ->
AEs = {image, {get, me2ae}, MEs},
general(edge, application, ToType, AEs);
general(edge, application, release, AEs) ->
{image, {get, ae}, AEs};
general(vertex, {line, _LineType}, ToType, L) ->
V = {partition_family, ?Q(1), {domain, L}},
general(vertex, function, ToType, V);
general(vertex, function, ToType, V) ->
M = {domain, V},
general(vertex, module, ToType, M);
general(vertex, module, ToType, M) ->
A = {image, {get, m2a}, M},
general(vertex, application, ToType, A);
general(vertex, application, release, A) ->
{image, {get, a2r}, A}.
special(_ObjectType, FromType, ToType, X) when FromType =:= ToType ->
X;
special(edge, {line, _LineType}, {line, all_line_call}, Calls) ->
{put, ?T(mods),
{projection,
?Q({external, fun({{{M1,_,_},{M2,_,_}},_}) -> {M1,M2} end}),
Calls},
{put, ?T(def_at),
{union, {image, {get, def_at},
{union, {domain, {get, ?T(mods)}},
{range, {get, ?T(mods)}}}}},
{fun funs_to_lines/2,
{get, ?T(def_at)}, Calls}}};
special(edge, function, {line, LineType}, VEs) ->
Var = if
LineType =:= line -> call_at;
LineType =:= export_call -> e_call_at;
LineType =:= local_call -> l_call_at;
LineType =:= external_call -> x_call_at
end,
line_edges(VEs, Var);
special(edge, module, ToType, MEs) ->
VEs = {image,
{projection,
?Q({external, fun(FE={{M1,_,_},{M2,_,_}}) -> {{M1,M2},FE} end}),
{union,
{image, {get, e},
{projection, ?Q({external, fun({M1,_M2}) -> M1 end}), MEs}}}},
MEs},
special(edge, function, ToType, VEs);
special(edge, application, ToType, AEs) ->
MEs = {inverse_image, {get, me2ae}, AEs},
special(edge, module, ToType, MEs);
special(edge, release, ToType, REs) ->
AEs = {inverse_image, {get, ae}, REs},
special(edge, application, ToType, AEs);
special(vertex, function, {line, _LineType}, V) ->
{restriction,
{union_of_family, {restriction, {get, def_at}, {domain, V}}},
{union_of_family, V}};
special(vertex, module, ToType, M) ->
V = {restriction, {get, v}, M},
special(vertex, function, ToType, V);
special(vertex, application, ToType, A) ->
M = {inverse_image, {get, m2a}, A},
special(vertex, module, ToType, M);
special(vertex, release, ToType, R) ->
A = {inverse_image, {get, a2r}, R},
special(vertex, application, ToType, A).
line_edges(VEs, CallAt) ->
{put, ?T(ves), VEs,
{put, ?T(m1),
{projection, ?Q({external, fun({{M1,_,_},_}) -> M1 end}),
{get, ?T(ves)}},
{image, {projection, ?Q({external, fun(C={VV,_L}) -> {VV,C} end}),
{union, {image, {get, CallAt}, {get, ?T(m1)}}}},
{get, ?T(ves)}}}}.
%% {(((v1,l1),(v2,l2)),l) :
%% (v1,l1) in DefAt and (v2,l2) in DefAt and ((v1,v2),L) in CallAt}
funs_to_lines(DefAt, CallAt) ->
T1 = multiple_relative_product({DefAt, DefAt}, projection(1, CallAt)),
T2 = composite(substitution(1, T1), CallAt),
Fun = fun({{{V1,V2},{L1,L2}},Ls}) -> {{{V1,L1},{V2,L2}},Ls} end,
projection({external, Fun}, T2).
what_type('Rel') -> release;
what_type('App') -> application;
what_type('Mod') -> module;
what_type('Fun') -> function;
what_type('Lin') -> {line, line};
what_type('LLin') -> {line, local_call};
what_type('XLin') -> {line, external_call};
what_type('ELin') -> {line, export_call};
what_type('XXL') -> {line, all_line_call}.
type_ord({line, all_line_call}) -> 0;
type_ord({line, _LT}) -> 1;
type_ord(function) -> 2;
type_ord(module) -> 3;
type_ord(application) -> 4;
type_ord(release) -> 5.
%% While evaluating, sets of vertices are represented as families.
%% Sets of edges are not families, but plain sets (this might change).
%% Calls (with line numbers) are "straightened" out here, but will be
%% families again shortly, unless just counted.
un_familiarize(function, vertex, E) ->
{union_of_family, E};
un_familiarize({line, _}, edge, E) ->
{family_to_relation, E};
un_familiarize(_Type, _OType, E) ->
E.
%% Expressions are evaluated using a stack and tail recursion.
%% Common subexpressions are evaluated once only, using a table for
%% storing temporary results.
%% (Using a table _and_ a stack is perhaps not a very good way of
%% doing things.)
i(E, Table) ->
Start = 1,
{N, _NE, _NI, NT} = find_nodes(E, Start, dict:new()),
{Vs, UVs0, L} = save_vars(dict:to_list(NT), NT, [], [], []),
VarsToSave = to_external(relation_to_family(relation(Vs))),
Fun = fun({NN,S}, D) ->
dict:store(NN, {extra,S,dict:fetch(NN, D)}, D)
end,
D = foldl(Fun, dict:from_list(L), VarsToSave),
UVs = reverse(sort(UVs0)),
{_D, Is0} = make_instructions(N, UVs, D),
Is = insert_unput(Is0),
?FORMAT("Instructions:~n~p~n~n~n", [Is]),
%% Well, compiles _and_ evaluates...
evaluate(Is, Table, []).
%% Traverses the expression tree in postorder, giving a unique number
%% to each node. A table is created, and common subexpressions found.
find_nodes(E={quote,_}, I, T) ->
find_node(E, I, T);
find_nodes({get, Var}, I, T) ->
find_node({var,Var}, I, T);
find_nodes({put, Var, E1, E2}, I, T) ->
{_NE1_N, NE1, I1, T1} = find_nodes(E1, I, T),
%% Now NE1 is considered used once, which is wrong. Fixed below.
NT = dict:store({var, Var}, NE1, T1),
find_nodes(E2, I1, NT);
find_nodes(Tuple, I, T) when is_tuple(Tuple) ->
[Tag0 | L] = tuple_to_list(Tuple),
Fun = fun(A, {L0, I0, T0}) ->
{NA, _E, NI, NT} = find_nodes(A, I0, T0),
{[NA | L0], NI, NT}
end,
{NL, NI, T1} = foldl(Fun, {[], I, T}, L),
Tag = case Tag0 of
_ when is_function(Tag0) ->
Tag0;
_ when is_atom(Tag0) ->
Arity = length(NL),
fun sofs:Tag0/Arity
end,
find_node({apply, Tag, NL}, NI, T1).
find_node(E, I, T) ->
case dict:find(E, T) of
{ok, {reuse, N}} ->
{N, E, I, T};
{ok, N} when is_integer(N) ->
{N, E, I, dict:store(E, {reuse, N}, T)};
{ok, E1} ->
find_node(E1, I, T);
error ->
{I, E, I+1, dict:store(E, I, T)}
end.
%% Creates save instructions for those values (stored on the stack while
%% evaluating) that are to be used after the result has been popped.
save_vars([{I, {reuse,N}} | DL], D, Vs, UVs, L) ->
save_vars(DL, D, [{N, {save, {tmp, N}}} | Vs], UVs, [{N, I} | L]);
save_vars([{I, N} | DL], D, Vs, UVs, L) when is_integer(N) ->
save_vars(DL, D, Vs, UVs, [{N, I} | L]);
save_vars([{{var,V={user,_}}, I} | DL], D, Vs, UVs, L) ->
N = case dict:fetch(I, D) of
{reuse, N0} -> N0;
N0 -> N0
end,
save_vars(DL, D, [{N, {save, V}} | Vs], [N | UVs], L);
save_vars([{{var,{tmp,_}}, _I} | DL], D, Vs, UVs, L) ->
save_vars(DL, D, Vs, UVs, L);
save_vars([], _D, Vs, UVs, L) ->
{Vs, UVs, L}.
%% Traverses the expression again, this time using more or less the
%% inverse of the table created by find_nodes. The first time a node
%% is visited, its children are traversed, the following times a
%% get instructions are inserted (using the saved value).
make_instructions(N, UserVars, D) ->
{D1, Is0} = make_instrs(N, D, []),
%% Assignments the results of which are not used by the final
%% expression are handled here. Instructions are created for user
%% variables only (assignment of a closure is handled properly
%% without further action).
make_more_instrs(UserVars, D1, Is0).
make_more_instrs([UV | UVs], D, Is) ->
case dict:find(UV, D) of
error ->
make_more_instrs(UVs, D, Is);
_Else ->
{ND, NIs} = make_instrs(UV, D, Is),
make_more_instrs(UVs, ND, [pop | NIs])
end;
make_more_instrs([], D, Is) ->
{D, Is}.
make_instrs(N, D, Is) ->
case dict:find(N, D) of
{ok, {extra, Save, Val}} ->
{D1, Is1} = make_instr(Val, D, Is),
{dict:erase(N, D1), Save ++ Is1};
{ok, Val} ->
{D1, Is1} = make_instr(Val, D, Is),
{dict:erase(N, D1), Is1};
error ->
{D, [{get, {tmp, N}} | Is]}
end.
make_instr({var, V}, D, Is) ->
{D, [{get, V} | Is]};
make_instr(Q = {quote, _T}, D, Is) ->
{D, [Q | Is]};
make_instr({apply, MF, Ns}, D, Is) ->
Fun = fun(N, {D0, Is0}) -> make_instrs(N, D0, Is0) end,
{D1, Is1} = foldl(Fun, {D, Is}, Ns),
{D1, [{apply, MF, length(Ns)} | Is1]}.
%% Makes sure that temporary results are removed from the table as soon
%% as they are no longer needed.
%% Assignments may create extra save instructions, which are removed here.
insert_unput(L) ->
insert_unput(L, dict:new(), []).
insert_unput([I={get, V={tmp, _}} | Is], D, L) ->
case dict:find(V, D) of
{ok, _} -> insert_unput(Is, D, [I | L]);
error -> insert_unput(Is, dict:store(V, [], D), [I, {unput, V} | L])
end;
insert_unput([I={save, V={tmp,_}} | Is], D, L) ->
case dict:find(V, D) of
{ok, _} ->
insert_unput(Is, dict:erase(V, D), [I | L]);
error ->
%% Extra save removed.
insert_unput(Is, dict:erase(V, D), L)
end;
insert_unput([I | Is], D, L) ->
insert_unput(Is, D, [I | L]);
insert_unput([], _D, L) ->
L.
graph_access(_G, V) ->
%% _G may have been deleted by an unput already
V.
evaluate([{apply, MF, NoAs} | P], T, S) ->
Args = sublist(S, NoAs),
NewS = nthtail(NoAs, S),
?FORMAT("Applying ~p/~p~n", [MF,NoAs]),
evaluate(P, T, [apply(MF, Args) | NewS]);
evaluate([{quote, Val} | P], T, S) ->
evaluate(P, T, [Val | S]);
evaluate([{get, Var} | P], T, S) when is_atom(Var) -> % predefined
Value = fetch_value(Var, T),
Val = case Value of
{R, _} -> R; % relation
_ -> Value % simple set
end,
evaluate(P, T, [Val | S]);
evaluate([{get, {inverse, Var}} | P], T, S) -> % predefined, inverse
{_, R} = fetch_value(Var, T),
evaluate(P, T, [R | S]);
evaluate([{get, {user, Var}} | P], T, S) ->
Val = fetch_value(Var, T),
evaluate(P, T, [Val | S]);
evaluate([{get, Var} | P], T, S) -> % tmp
evaluate(P, T, [dict:fetch(Var, T) | S]);
evaluate([{save, Var={tmp, _}} | P], T, S=[Val | _]) ->
T1 = update_graph_counter(Val, +1, T),
evaluate(P, dict:store(Var, Val, T1), S);
evaluate([{save, {user, Name}} | P], T, S=[Val | _]) ->
#xref_var{vtype = user, otype = OType, type = Type} = dict:fetch(Name, T),
NewVar = #xref_var{name = Name, value = Val,
vtype = user, otype = OType, type = Type},
T1 = update_graph_counter(Val, +1, T),
NT = dict:store(Name, NewVar, T1),
evaluate(P, NT, S);
evaluate([{unput, Var} | P], T, S) ->
T1 = update_graph_counter(dict:fetch(Var, T), -1, T),
evaluate(P, dict:erase(Var, T1), S);
evaluate([pop | P], T, [_ | S]) ->
evaluate(P, T, S);
evaluate([], T, [R]) ->
{T, R}.
%% (PossibleGraph, 1 | -1, dict:dict()) -> dict:dict()
%% Use the same table for everything... Here: Reference counters for digraphs.
update_graph_counter(Value, Inc, T) ->
case catch digraph:info(Value) of
Info when is_list(Info) ->
case dict:find(Value, T) of
{ok, 1} when Inc =:= -1 ->
true = digraph:delete(Value),
dict:erase(Value, T);
{ok, C} ->
dict:store(Value, C+Inc, T);
error when Inc =:= 1 ->
dict:store(Value, 1, T)
end;
_EXIT ->
T
end.
fetch_value(V, D) ->
#xref_var{value = Value} = dict:fetch(V, D),
Value.
format_parse_error(["invalid_regexp", String, Error], Line) ->
io_lib:format("Invalid regular expression \"~ts\"~s: ~ts~n",
[String, Line, lists:flatten(Error)]);
format_parse_error(["invalid_regexp_variable", Var], Line) ->
io_lib:format("Invalid wildcard variable ~tp~s "
"(only '_' is allowed)~n", [Var, Line]);
format_parse_error(["missing_type", Expr], Line) ->
io_lib:format("Missing type of regular expression ~ts~s~n",
[Expr, Line]);
format_parse_error(["type_mismatch", Expr], Line) ->
io_lib:format("Type does not match structure of constant~s: ~ts~n",
[Line, Expr]);
format_parse_error(["invalid_operator", Op], Line) ->
io_lib:format("Invalid operator ~tp~s~n", [Op, Line]);
format_parse_error(Error, Line) ->
io_lib:format("Parse error~s: ~ts~n", [Line, lists:flatten(Error)]).
format_line(?XREF_END_LINE) ->
" at end of string";
format_line(0) ->
"";
format_line(Line) when is_integer(Line) ->
concat([" on line ", Line]).
throw_error(Reason) ->
throw(error(Reason)).
error(Reason) ->
{error, ?MODULE, Reason}.