-module(graded@internal@effect_term). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "src/graded/internal/effect_term.gleam"). -export([pure/0, unknown/0, from_effect_set/1, free_vars/1, subst/2, normalize/1, to_effect_set/1, normalize_bounded/2]). -if(?OTP_RELEASE >= 27). -define(MODULEDOC(Str), -moduledoc(Str)). -define(DOC(Str), -doc(Str)). -else. -define(MODULEDOC(Str), -compile([])). -define(DOC(Str), -compile([])). -endif. ?MODULEDOC(false). -file("src/graded/internal/effect_term.gleam", 31). ?DOC(false). -spec pure() -> graded@internal@types:effect_term(). pure() -> {t_labels, gleam@set:new()}. -file("src/graded/internal/effect_term.gleam", 36). ?DOC(false). -spec unknown() -> graded@internal@types:effect_term(). unknown() -> {t_labels, gleam@set:from_list([<<"Unknown"/utf8>>])}. -file("src/graded/internal/effect_term.gleam", 326). ?DOC(false). -spec dedup_adjacent(list({binary(), graded@internal@types:effect_term()})) -> list({binary(), graded@internal@types:effect_term()}). dedup_adjacent(Keyed) -> _pipe = Keyed, _pipe@1 = gleam@list:fold(_pipe, [], fun(Acc, Pair) -> case Acc of [{Previous, _} | _] when Previous =:= erlang:element(1, Pair) -> Acc; _ -> [Pair | Acc] end end), lists:reverse(_pipe@1). -file("src/graded/internal/effect_term.gleam", 343). ?DOC(false). -spec term_key(graded@internal@types:effect_term()) -> binary(). term_key(Term) -> case Term of t_top -> <<"T"/utf8>>; {t_labels, Labels} -> <<<<"L["/utf8, (begin _pipe = Labels, _pipe@1 = gleam@set:to_list(_pipe), _pipe@2 = gleam@list:sort( _pipe@1, fun gleam@string:compare/2 ), gleam@string:join(_pipe@2, <<","/utf8>>) end)/binary>>/binary, "]"/utf8>>; {t_var, Name} -> <<<<"V("/utf8, Name/binary>>/binary, ")"/utf8>>; {t_app, Operator, Arg} -> <<<<<<<<"A("/utf8, (term_key(Operator))/binary>>/binary, " "/utf8>>/binary, (term_key(Arg))/binary>>/binary, ")"/utf8>>; {t_abs, Param, Body} -> <<<<<<<<"F("/utf8, Param/binary>>/binary, "."/utf8>>/binary, (term_key(Body))/binary>>/binary, ")"/utf8>>; {t_union, Terms} -> <<<<"U("/utf8, (begin _pipe@3 = Terms, _pipe@4 = gleam@list:map(_pipe@3, fun term_key/1), _pipe@5 = gleam@list:sort( _pipe@4, fun gleam@string:compare/2 ), gleam@string:join(_pipe@5, <<"|"/utf8>>) end)/binary>>/binary, ")"/utf8>> end. -file("src/graded/internal/effect_term.gleam", 277). ?DOC(false). -spec flatten_union(list(graded@internal@types:effect_term())) -> graded@internal@types:effect_term(). flatten_union(Members) -> Flat = gleam@list:flat_map(Members, fun(Member) -> case Member of {t_union, Inner} -> Inner; _ -> [Member] end end), case gleam@list:any(Flat, fun(M) -> M =:= t_top end) of true -> t_top; false -> {Label_members, Other_members} = gleam@list:partition( Flat, fun(M@1) -> case M@1 of {t_labels, _} -> true; _ -> false end end ), Merged_labels = gleam@list:fold( Label_members, gleam@set:new(), fun(Acc, M@2) -> case M@2 of {t_labels, Labels} -> gleam@set:union(Acc, Labels); _ -> Acc end end ), Others = begin _pipe = Other_members, _pipe@1 = gleam@list:map( _pipe, fun(M@3) -> {term_key(M@3), M@3} end ), _pipe@2 = gleam@list:sort( _pipe@1, fun(A, B) -> gleam@string:compare( erlang:element(1, A), erlang:element(1, B) ) end ), _pipe@3 = dedup_adjacent(_pipe@2), gleam@list:map( _pipe@3, fun(Pair) -> erlang:element(2, Pair) end ) end, Label_part = case gleam@set:is_empty(Merged_labels) of true -> []; false -> [{t_labels, Merged_labels}] end, case lists:append(Label_part, Others) of [] -> pure(); [Single] -> Single; All -> {t_union, All} end end. -file("src/graded/internal/effect_term.gleam", 45). ?DOC(false). -spec from_effect_set(graded@internal@types:effect_set()) -> graded@internal@types:effect_term(). from_effect_set(Effect_set) -> case Effect_set of wildcard -> t_top; {specific, Labels} -> {t_labels, Labels}; {polymorphic, Labels@1, Variables} -> Var_terms = begin _pipe = Variables, _pipe@1 = gleam@set:to_list(_pipe), gleam@list:map(_pipe@1, fun(Field@0) -> {t_var, Field@0} end) end, flatten_union([{t_labels, Labels@1} | Var_terms]) end. -file("src/graded/internal/effect_term.gleam", 256). ?DOC(false). -spec is_abstraction(graded@internal@types:effect_term()) -> boolean(). is_abstraction(Term) -> case Term of {t_abs, _, _} -> true; _ -> false end. -file("src/graded/internal/effect_term.gleam", 163). ?DOC(false). -spec fresh_loop(binary(), gleam@set:set(binary()), integer()) -> binary(). fresh_loop(Base, Avoid, N) -> Candidate = <>, case gleam@set:contains(Avoid, Candidate) of true -> fresh_loop(Base, Avoid, N + 1); false -> Candidate end. -file("src/graded/internal/effect_term.gleam", 159). ?DOC(false). -spec fresh(binary(), gleam@set:set(binary())) -> binary(). fresh(Base, Avoid) -> fresh_loop(Base, Avoid, 0). -file("src/graded/internal/effect_term.gleam", 87). ?DOC(false). -spec free_vars(graded@internal@types:effect_term()) -> gleam@set:set(binary()). free_vars(Term) -> case Term of {t_labels, _} -> gleam@set:new(); t_top -> gleam@set:new(); {t_var, Name} -> gleam@set:from_list([Name]); {t_app, Operator, Arg} -> gleam@set:union(free_vars(Operator), free_vars(Arg)); {t_union, Terms} -> gleam@list:fold( Terms, gleam@set:new(), fun(Acc, T) -> gleam@set:union(Acc, free_vars(T)) end ); {t_abs, Param, Body} -> gleam@set:delete(free_vars(Body), Param) end. -file("src/graded/internal/effect_term.gleam", 124). ?DOC(false). -spec subst_abs( binary(), graded@internal@types:effect_term(), gleam@dict:dict(binary(), graded@internal@types:effect_term()) ) -> graded@internal@types:effect_term(). subst_abs(Param, Body, Bindings) -> Inner = gleam@dict:delete(Bindings, Param), gleam@bool:guard( gleam@dict:is_empty(Inner), {t_abs, Param, Body}, fun() -> Body_fv = free_vars(Body), Incoming = gleam@dict:fold( Inner, gleam@set:new(), fun(Acc, Key, Value) -> case gleam@set:contains(Body_fv, Key) of true -> gleam@set:union(Acc, free_vars(Value)); false -> Acc end end ), case gleam@set:contains(Incoming, Param) of false -> {t_abs, Param, subst(Body, Inner)}; true -> Avoid = gleam@set:union(Incoming, Body_fv), Renamed_param = fresh(Param, Avoid), Renamed_body = subst( Body, maps:from_list([{Param, {t_var, Renamed_param}}]) ), {t_abs, Renamed_param, subst(Renamed_body, Inner)} end end ). -file("src/graded/internal/effect_term.gleam", 106). ?DOC(false). -spec subst( graded@internal@types:effect_term(), gleam@dict:dict(binary(), graded@internal@types:effect_term()) ) -> graded@internal@types:effect_term(). subst(Term, Bindings) -> case Term of {t_labels, _} -> Term; t_top -> Term; {t_var, Name} -> case gleam_stdlib:map_get(Bindings, Name) of {ok, Replacement} -> Replacement; {error, nil} -> Term end; {t_app, Operator, Arg} -> {t_app, subst(Operator, Bindings), subst(Arg, Bindings)}; {t_union, Terms} -> {t_union, gleam@list:map( Terms, fun(_capture) -> subst(_capture, Bindings) end )}; {t_abs, Param, Body} -> subst_abs(Param, Body, Bindings) end. -file("src/graded/internal/effect_term.gleam", 263). ?DOC(false). -spec reduce_each(list(graded@internal@types:effect_term()), integer()) -> {list(graded@internal@types:effect_term()), integer()}. reduce_each(Terms, Fuel) -> {Acc, Final_fuel} = gleam@list:fold( Terms, {[], Fuel}, fun(State, T) -> {Done, Remaining} = State, {Reduced, Remaining1} = reduce(T, Remaining), {[Reduced | Done], Remaining1} end ), {lists:reverse(Acc), Final_fuel}. -file("src/graded/internal/effect_term.gleam", 218). ?DOC(false). -spec reduce_app( graded@internal@types:effect_term(), graded@internal@types:effect_term(), integer() ) -> {graded@internal@types:effect_term(), integer()}. reduce_app(Operator, Arg, Fuel) -> {Reduced_fn, Fuel1} = reduce(Operator, Fuel - 1), {Reduced_arg, Fuel2} = reduce(Arg, Fuel1), case Reduced_fn of {t_abs, Param, Body} -> reduce( subst(Body, maps:from_list([{Param, Reduced_arg}])), Fuel2 - 1 ); {t_union, Members} -> case gleam@list:all(Members, fun is_abstraction/1) of true -> Applied = {t_union, gleam@list:map( Members, fun(M) -> {t_app, M, Reduced_arg} end )}, reduce(Applied, Fuel2 - 1); false -> {{t_app, Reduced_fn, Reduced_arg}, Fuel2} end; _ -> {{t_app, Reduced_fn, Reduced_arg}, Fuel2} end. -file("src/graded/internal/effect_term.gleam", 196). ?DOC(false). -spec reduce(graded@internal@types:effect_term(), integer()) -> {graded@internal@types:effect_term(), integer()}. reduce(Term, Fuel) -> gleam@bool:guard(Fuel =< 0, {unknown(), -1}, fun() -> case Term of {t_labels, _} -> {Term, Fuel}; t_top -> {Term, Fuel}; {t_var, _} -> {Term, Fuel}; {t_abs, Param, Body} -> {Reduced_body, Fuel1} = reduce(Body, Fuel), {{t_abs, Param, Reduced_body}, Fuel1}; {t_app, Operator, Arg} -> reduce_app(Operator, Arg, Fuel); {t_union, Terms} -> {Reduced, Fuel1@1} = reduce_each(Terms, Fuel), {flatten_union(Reduced), Fuel1@1} end end). -file("src/graded/internal/effect_term.gleam", 177). ?DOC(false). -spec normalize(graded@internal@types:effect_term()) -> graded@internal@types:effect_term(). normalize(Term) -> {Result, _} = reduce(Term, 1000000), Result. -file("src/graded/internal/effect_term.gleam", 66). ?DOC(false). -spec term_to_set(graded@internal@types:effect_term()) -> graded@internal@types:effect_set(). term_to_set(Normalized) -> case Normalized of t_top -> wildcard; {t_labels, Labels} -> {specific, Labels}; {t_var, Name} -> {polymorphic, gleam@set:new(), gleam@set:from_list([Name])}; {t_app, _, _} -> {specific, gleam@set:from_list([<<"Unknown"/utf8>>])}; {t_abs, _, _} -> {specific, gleam@set:from_list([<<"Unknown"/utf8>>])}; {t_union, Members} -> gleam@list:fold( Members, graded@internal@types:empty(), fun(Acc, Member) -> graded@internal@types:union(Acc, term_to_set(Member)) end ) end. -file("src/graded/internal/effect_term.gleam", 62). ?DOC(false). -spec to_effect_set(graded@internal@types:effect_term()) -> graded@internal@types:effect_set(). to_effect_set(Term) -> term_to_set(normalize(Term)). -file("src/graded/internal/effect_term.gleam", 185). ?DOC(false). -spec normalize_bounded(graded@internal@types:effect_term(), integer()) -> {ok, graded@internal@types:effect_term()} | {error, nil}. normalize_bounded(Term, Fuel) -> {Result, Remaining} = reduce(Term, Fuel), case Remaining < 0 of true -> {error, nil}; false -> {ok, Result} end.