-module(graded@internal@effects). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "src/graded/internal/effects.gleam"). -export([lookup_type_field/3, with_type_fields/2, with_externals/2, lookup/2, lookup_effects/2, format_effect_set/1, parse_path_dependencies/1, with_inferred/2, parse_semver/1, compare_semver/2, semver_lte/2, pick_best_version/2, load_knowledge_base/1, empty_knowledge_base/0]). -export_type([effect_lookup/0, knowledge_base/0]). -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). -type effect_lookup() :: {known, graded@internal@types:effect_set()} | unknown. -type knowledge_base() :: {knowledge_base, gleam@dict:dict(graded@internal@types:qualified_name(), graded@internal@types:effect_set()), gleam@dict:dict(graded@internal@types:qualified_name(), list(graded@internal@types:param_bound())), gleam@dict:dict({binary(), binary()}, graded@internal@types:effect_set()), gleam@set:set(binary())}. -file("src/graded/internal/effects.gleam", 59). ?DOC(false). -spec lookup_type_field(knowledge_base(), binary(), binary()) -> effect_lookup(). lookup_type_field(Knowledge_base, Type_name, Field) -> case gleam_stdlib:map_get( erlang:element(4, Knowledge_base), {Type_name, Field} ) of {ok, Effect_set} -> {known, Effect_set}; {error, nil} -> unknown end. -file("src/graded/internal/effects.gleam", 71). ?DOC(false). -spec with_type_fields( knowledge_base(), list(graded@internal@types:type_field_annotation()) ) -> knowledge_base(). with_type_fields(Knowledge_base, Type_fields) -> Merged = gleam@list:fold( Type_fields, erlang:element(4, Knowledge_base), fun(Accumulator, Type_field) -> gleam@dict:insert( Accumulator, {erlang:element(2, Type_field), erlang:element(3, Type_field)}, erlang:element(4, Type_field) ) end ), {knowledge_base, erlang:element(2, Knowledge_base), erlang:element(3, Knowledge_base), Merged, erlang:element(5, Knowledge_base)}. -file("src/graded/internal/effects.gleam", 93). ?DOC(false). -spec with_externals( knowledge_base(), list(graded@internal@types:external_annotation()) ) -> knowledge_base(). with_externals(Knowledge_base, Externals) -> {Effect_map@1, Pure_set@1} = gleam@list:fold( Externals, {erlang:element(2, Knowledge_base), erlang:element(5, Knowledge_base)}, fun(Accumulator, External_annotation) -> {Effect_map, Pure_set} = Accumulator, case erlang:element(3, External_annotation) of module_external -> {Effect_map, gleam@set:insert( Pure_set, erlang:element(2, External_annotation) )}; {function_external, Function} -> {gleam@dict:insert( Effect_map, {qualified_name, erlang:element(2, External_annotation), Function}, erlang:element(4, External_annotation) ), Pure_set} end end ), {knowledge_base, Effect_map@1, erlang:element(3, Knowledge_base), erlang:element(4, Knowledge_base), Pure_set@1}. -file("src/graded/internal/effects.gleam", 127). ?DOC(false). -spec lookup(knowledge_base(), graded@internal@types:qualified_name()) -> effect_lookup(). lookup(Knowledge_base, Name) -> case gleam_stdlib:map_get(erlang:element(2, Knowledge_base), Name) of {ok, Effect_set} -> {known, Effect_set}; {error, nil} -> case gleam@set:contains( erlang:element(5, Knowledge_base), erlang:element(2, Name) ) of true -> {known, graded@internal@types:empty()}; false -> unknown end end. -file("src/graded/internal/effects.gleam", 142). ?DOC(false). -spec lookup_effects(knowledge_base(), graded@internal@types:qualified_name()) -> graded@internal@types:effect_set(). lookup_effects(Knowledge_base, Name) -> case lookup(Knowledge_base, Name) of {known, Effect_set} -> Effect_set; unknown -> graded@internal@types:from_labels([<<"Unknown"/utf8>>]) end. -file("src/graded/internal/effects.gleam", 153). ?DOC(false). -spec format_effect_set(graded@internal@types:effect_set()) -> binary(). format_effect_set(Effect_set) -> case Effect_set of wildcard -> <<"[_]"/utf8>>; {specific, Labels} -> case begin _pipe = gleam@set:to_list(Labels), gleam@list:sort(_pipe, fun gleam@string:compare/2) end of [] -> <<"[]"/utf8>>; Sorted -> <<<<"["/utf8, (gleam@string:join(Sorted, <<", "/utf8>>))/binary>>/binary, "]"/utf8>> end end. -file("src/graded/internal/effects.gleam", 166). ?DOC(false). -spec parse_path_dependencies(binary()) -> list({binary(), binary()}). parse_path_dependencies(Gleam_toml_path) -> Parsed = begin gleam@result:'try'( begin _pipe = simplifile:read(Gleam_toml_path), gleam@result:map_error(_pipe, fun(_) -> nil end) end, fun(Content) -> gleam@result:'try'( begin _pipe@1 = tom:parse(Content), gleam@result:map_error(_pipe@1, fun(_) -> nil end) end, fun(Toml) -> gleam@result:'try'( begin _pipe@2 = tom:get_table( Toml, [<<"dependencies"/utf8>>] ), gleam@result:map_error( _pipe@2, fun(_) -> nil end ) end, fun(Deps) -> {ok, gleam@dict:fold( Deps, [], fun(Acc, Name, Value) -> case Value of {inline_table, Table} -> case tom:get_string( Table, [<<"path"/utf8>>] ) of {ok, Path} -> [{Name, Path} | Acc]; {error, _} -> Acc end; _ -> Acc end end )} end ) end ) end ) end, gleam@result:unwrap(Parsed, []). -file("src/graded/internal/effects.gleam", 197). ?DOC(false). -spec with_inferred( knowledge_base(), gleam@dict:dict(graded@internal@types:qualified_name(), graded@internal@types:effect_set()) ) -> knowledge_base(). with_inferred(Knowledge_base, Inferred) -> Merged = maps:merge(Inferred, erlang:element(2, Knowledge_base)), {knowledge_base, Merged, erlang:element(3, Knowledge_base), erlang:element(4, Knowledge_base), erlang:element(5, Knowledge_base)}. -file("src/graded/internal/effects.gleam", 254). ?DOC(false). -spec fold_annotation( {gleam@dict:dict(graded@internal@types:qualified_name(), graded@internal@types:effect_set()), gleam@dict:dict(graded@internal@types:qualified_name(), list(graded@internal@types:param_bound()))}, graded@internal@types:effect_annotation(), binary() ) -> {gleam@dict:dict(graded@internal@types:qualified_name(), graded@internal@types:effect_set()), gleam@dict:dict(graded@internal@types:qualified_name(), list(graded@internal@types:param_bound()))}. fold_annotation(Accumulator, Annotation, Module_path) -> {Effect_map, Param_map} = Accumulator, Qualified_name = {qualified_name, Module_path, erlang:element(3, Annotation)}, case erlang:element(2, Annotation) of effects -> {gleam@dict:insert( Effect_map, Qualified_name, erlang:element(5, Annotation) ), Param_map}; check -> case erlang:element(4, Annotation) of [] -> Accumulator; Params -> {Effect_map, gleam@dict:insert(Param_map, Qualified_name, Params)} end end. -file("src/graded/internal/effects.gleam", 278). ?DOC(false). -spec file_path_to_module(binary(), binary()) -> binary(). file_path_to_module(Path, Graded_directory) -> Prefix = <>, Relative = case gleam_stdlib:string_starts_with(Path, Prefix) of true -> gleam@string:drop_start(Path, string:length(Prefix)); false -> Path end, gleam@string:replace(Relative, <<".graded"/utf8>>, <<""/utf8>>). -file("src/graded/internal/effects.gleam", 230). ?DOC(false). -spec load_graded_file( {gleam@dict:dict(graded@internal@types:qualified_name(), graded@internal@types:effect_set()), gleam@dict:dict(graded@internal@types:qualified_name(), list(graded@internal@types:param_bound()))}, binary(), binary() ) -> {gleam@dict:dict(graded@internal@types:qualified_name(), graded@internal@types:effect_set()), gleam@dict:dict(graded@internal@types:qualified_name(), list(graded@internal@types:param_bound()))}. load_graded_file(Maps, File_path, Graded_directory) -> Parsed = begin _pipe = simplifile:read(File_path), _pipe@1 = gleam@result:map_error(_pipe, fun(_) -> nil end), gleam@result:'try'( _pipe@1, fun(Content) -> _pipe@2 = graded@internal@annotation:parse_file(Content), gleam@result:map_error(_pipe@2, fun(_) -> nil end) end ) end, case Parsed of {error, _} -> Maps; {ok, Graded_file} -> Module_path = file_path_to_module(File_path, Graded_directory), {Effect_map, Param_map} = Maps, _pipe@3 = graded@internal@annotation:extract_annotations( Graded_file ), gleam@list:fold( _pipe@3, {Effect_map, Param_map}, fun(Accumulator, Annotation) -> fold_annotation(Accumulator, Annotation, Module_path) end ) end. -file("src/graded/internal/effects.gleam", 207). ?DOC(false). -spec load_dependency_effects(binary()) -> {gleam@dict:dict(graded@internal@types:qualified_name(), graded@internal@types:effect_set()), gleam@dict:dict(graded@internal@types:qualified_name(), list(graded@internal@types:param_bound()))}. load_dependency_effects(Packages_directory) -> Entries = case simplifile_erl:read_directory(Packages_directory) of {ok, Found} -> Found; {error, _} -> [] end, gleam@list:fold( Entries, {maps:new(), maps:new()}, fun(Maps, Package_name) -> Graded_directory = <<<<<>/binary, Package_name/binary>>/binary, "/priv/graded"/utf8>>, Files = case simplifile:get_files(Graded_directory) of {ok, Found@1} -> Found@1; {error, _} -> [] end, gleam@list:fold( Files, Maps, fun(Inner_maps, File_path) -> case gleam_stdlib:string_ends_with( File_path, <<".graded"/utf8>> ) of true -> load_graded_file( Inner_maps, File_path, Graded_directory ); false -> Inner_maps end end ) end ). -file("src/graded/internal/effects.gleam", 287). ?DOC(false). -spec find_catalog_directory() -> binary(). find_catalog_directory() -> Installed = <<"build/packages/graded/priv/catalog"/utf8>>, case simplifile_erl:is_directory(Installed) of {ok, true} -> Installed; _ -> <<"priv/catalog"/utf8>> end. -file("src/graded/internal/effects.gleam", 396). ?DOC(false). -spec parse_semver(binary()) -> {integer(), integer(), integer()}. parse_semver(Version) -> case gleam@string:split(Version, <<"."/utf8>>) of [Major, Minor, Patch] -> {begin _pipe = gleam_stdlib:parse_int(Major), gleam@result:unwrap(_pipe, 0) end, begin _pipe@1 = gleam_stdlib:parse_int(Minor), gleam@result:unwrap(_pipe@1, 0) end, begin _pipe@2 = gleam_stdlib:parse_int(Patch), gleam@result:unwrap(_pipe@2, 0) end}; [Major@1, Minor@1] -> {begin _pipe@3 = gleam_stdlib:parse_int(Major@1), gleam@result:unwrap(_pipe@3, 0) end, begin _pipe@4 = gleam_stdlib:parse_int(Minor@1), gleam@result:unwrap(_pipe@4, 0) end, 0}; _ -> {0, 0, 0} end. -file("src/graded/internal/effects.gleam", 416). ?DOC(false). -spec compare_semver( {integer(), integer(), integer()}, {integer(), integer(), integer()} ) -> gleam@order:order(). compare_semver(Left, Right) -> case gleam@int:compare(erlang:element(1, Left), erlang:element(1, Right)) of eq -> case gleam@int:compare( erlang:element(2, Left), erlang:element(2, Right) ) of eq -> gleam@int:compare( erlang:element(3, Left), erlang:element(3, Right) ); Other -> Other end; Other@1 -> Other@1 end. -file("src/graded/internal/effects.gleam", 412). ?DOC(false). -spec semver_lte( {integer(), integer(), integer()}, {integer(), integer(), integer()} ) -> boolean(). semver_lte(Left, Right) -> compare_semver(Left, Right) /= gt. -file("src/graded/internal/effects.gleam", 375). ?DOC(false). -spec pick_best_version( list({{integer(), integer(), integer()}, binary()}), {integer(), integer(), integer()} ) -> {ok, binary()} | {error, nil}. pick_best_version(Versions, Installed) -> Eligible = begin _pipe = gleam@list:filter( Versions, fun(Version) -> semver_lte(erlang:element(1, Version), Installed) end ), gleam@list:sort( _pipe, fun(Left, Right) -> compare_semver( erlang:element(1, Right), erlang:element(1, Left) ) end ) end, case Eligible of [Best | _] -> {ok, erlang:element(2, Best)}; [] -> case gleam@list:sort( Versions, fun(Left@1, Right@1) -> compare_semver( erlang:element(1, Right@1), erlang:element(1, Left@1) ) end ) of [Best@1 | _] -> {ok, erlang:element(2, Best@1)}; [] -> {error, nil} end end. -file("src/graded/internal/effects.gleam", 332). ?DOC(false). -spec resolve_catalog_files(list(binary()), gleam@dict:dict(binary(), binary())) -> list(binary()). resolve_catalog_files(Catalog_files, Installed_versions) -> Parsed = gleam@list:filter_map( Catalog_files, fun(Path) -> Filename = begin _pipe = Path, _pipe@1 = gleam@string:split(_pipe, <<"/"/utf8>>), _pipe@2 = gleam@list:last(_pipe@1), _pipe@3 = gleam@result:unwrap(_pipe@2, <<""/utf8>>), gleam@string:replace(_pipe@3, <<".graded"/utf8>>, <<""/utf8>>) end, case gleam@string:split(Filename, <<"@"/utf8>>) of [Package, Version] -> {ok, {Package, parse_semver(Version), Path}}; _ -> {error, nil} end end ), Grouped = gleam@list:fold( Parsed, maps:new(), fun(Accumulator, Entry) -> {Package@1, Version@1, Path@1} = Entry, Existing = begin _pipe@4 = gleam_stdlib:map_get(Accumulator, Package@1), gleam@result:unwrap(_pipe@4, []) end, gleam@dict:insert( Accumulator, Package@1, [{Version@1, Path@1} | Existing] ) end ), gleam@dict:fold( Grouped, [], fun(Selected, Package@2, Versions) -> case gleam_stdlib:map_get(Installed_versions, Package@2) of {error, nil} -> Selected; {ok, Installed_str} -> Installed = parse_semver(Installed_str), Best = pick_best_version(Versions, Installed), case Best of {ok, Path@2} -> [Path@2 | Selected]; {error, nil} -> Selected end end end ). -file("src/graded/internal/effects.gleam", 430). ?DOC(false). -spec parse_manifest_versions(binary()) -> gleam@dict:dict(binary(), binary()). parse_manifest_versions(Manifest_path) -> Parsed = begin gleam@result:'try'( begin _pipe = simplifile:read(Manifest_path), gleam@result:map_error(_pipe, fun(_) -> nil end) end, fun(Content) -> gleam@result:'try'( begin _pipe@1 = tom:parse(Content), gleam@result:map_error(_pipe@1, fun(_) -> nil end) end, fun(Toml) -> gleam@result:'try'( begin _pipe@2 = tom:get_array( Toml, [<<"packages"/utf8>>] ), gleam@result:map_error( _pipe@2, fun(_) -> nil end ) end, fun(Packages) -> {ok, gleam@list:fold( Packages, maps:new(), fun(Accumulator, Package) -> case Package of {inline_table, Table} -> case {tom:get_string( Table, [<<"name"/utf8>>] ), tom:get_string( Table, [<<"version"/utf8>>] )} of {{ok, Name}, {ok, Version}} -> gleam@dict:insert( Accumulator, Name, Version ); {_, _} -> Accumulator end; _ -> Accumulator end end )} end ) end ) end ) end, gleam@result:unwrap(Parsed, maps:new()). -file("src/graded/internal/effects.gleam", 295). ?DOC(false). -spec load_catalog(binary(), binary()) -> {gleam@dict:dict(graded@internal@types:qualified_name(), graded@internal@types:effect_set()), gleam@set:set(binary())}. load_catalog(Catalog_dir, Manifest_path) -> Installed_versions = parse_manifest_versions(Manifest_path), Catalog_files = case simplifile:get_files(Catalog_dir) of {ok, Files} -> gleam@list:filter( Files, fun(File) -> gleam_stdlib:string_ends_with(File, <<".graded"/utf8>>) end ); {error, _} -> [] end, Selected = resolve_catalog_files(Catalog_files, Installed_versions), All_externals = gleam@list:flat_map( Selected, fun(File_path) -> case simplifile:read(File_path) of {error, _} -> []; {ok, Content} -> case graded@internal@annotation:parse_file(Content) of {error, _} -> []; {ok, Graded_file} -> graded@internal@annotation:extract_externals( Graded_file ) end end end ), Empty_kb = {knowledge_base, maps:new(), maps:new(), maps:new(), gleam@set:new()}, Merged = with_externals(Empty_kb, All_externals), {erlang:element(2, Merged), erlang:element(5, Merged)}. -file("src/graded/internal/effects.gleam", 34). ?DOC(false). -spec load_knowledge_base(binary()) -> knowledge_base(). load_knowledge_base(Packages_directory) -> {Dep_effects, Dep_params} = load_dependency_effects(Packages_directory), Catalog_dir = find_catalog_directory(), {Cat_effects, Cat_pure} = load_catalog( Catalog_dir, <<"manifest.toml"/utf8>> ), {knowledge_base, maps:merge(Dep_effects, Cat_effects), Dep_params, maps:new(), Cat_pure}. -file("src/graded/internal/effects.gleam", 47). ?DOC(false). -spec empty_knowledge_base() -> knowledge_base(). empty_knowledge_base() -> Catalog_dir = find_catalog_directory(), {Cat_effects, Cat_pure} = load_catalog( Catalog_dir, <<"manifest.toml"/utf8>> ), {knowledge_base, Cat_effects, maps:new(), maps:new(), Cat_pure}.