%%
%% %CopyrightBegin%
%%
%% SPDX-License-Identifier: Apache-2.0
%%
%% Copyright Ericsson AB 2000-2025. 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).
-moduledoc false.

-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}.
