-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, load_spec_effects_from_file/1, load_spec_effects/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", 60). ?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", 72). ?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(3, Type_field), erlang:element(4, Type_field)}, erlang:element(5, 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", 94). ?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", 128). ?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", 143). ?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", 154). ?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", 167). ?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", 217). ?DOC(false). -spec fold_spec_effects(list(graded@internal@types:effect_annotation())) -> gleam@dict:dict(graded@internal@types:qualified_name(), graded@internal@types:effect_set()). fold_spec_effects(Annotations) -> gleam@list:fold( Annotations, maps:new(), fun(Acc, Ann) -> case erlang:element(2, Ann) of effects -> case graded@internal@annotation:split_qualified_name( erlang:element(3, Ann) ) of {ok, {Module, Function}} -> gleam@dict:insert( Acc, {qualified_name, Module, Function}, erlang:element(5, Ann) ); {error, _} -> Acc end; check -> Acc end end ). -file("src/graded/internal/effects.gleam", 211). ?DOC(false). -spec load_spec_effects_from_file(graded@internal@types:graded_file()) -> gleam@dict:dict(graded@internal@types:qualified_name(), graded@internal@types:effect_set()). load_spec_effects_from_file(File) -> fold_spec_effects(graded@internal@annotation:extract_annotations(File)). -file("src/graded/internal/effects.gleam", 233). ?DOC(false). -spec read_spec_annotations(binary()) -> {ok, list(graded@internal@types:effect_annotation())} | {error, nil}. read_spec_annotations(Spec_path) -> gleam@result:'try'( begin _pipe = simplifile:read(Spec_path), gleam@result:replace_error(_pipe, nil) end, fun(Content) -> gleam@result:'try'( begin _pipe@1 = graded@internal@annotation:parse_file(Content), gleam@result:replace_error(_pipe@1, nil) end, fun(File) -> {ok, graded@internal@annotation:extract_annotations(File)} end ) end ). -file("src/graded/internal/effects.gleam", 201). ?DOC(false). -spec load_spec_effects(binary()) -> gleam@dict:dict(graded@internal@types:qualified_name(), graded@internal@types:effect_set()). load_spec_effects(Spec_path) -> case read_spec_annotations(Spec_path) of {error, _} -> maps:new(); {ok, Annotations} -> fold_spec_effects(Annotations) end. -file("src/graded/internal/effects.gleam", 247). ?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", 290). ?DOC(false). -spec fold_qualified_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() ) -> {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_qualified_annotation(Accumulator, Ann) -> {Effect_map, Param_map} = Accumulator, case graded@internal@annotation:split_qualified_name(erlang:element(3, Ann)) of {error, _} -> Accumulator; {ok, {Module, Function}} -> Qualified_name = {qualified_name, Module, Function}, case erlang:element(2, Ann) of effects -> {gleam@dict:insert( Effect_map, Qualified_name, erlang:element(5, Ann) ), Param_map}; check -> case erlang:element(4, Ann) of [] -> Accumulator; Params -> {Effect_map, gleam@dict:insert( Param_map, Qualified_name, Params )} end end end. -file("src/graded/internal/effects.gleam", 280). ?DOC(false). -spec load_spec_into_maps( {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() ) -> {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_spec_into_maps(Maps, Spec_path) -> case read_spec_annotations(Spec_path) of {error, _} -> Maps; {ok, Annotations} -> gleam@list:fold(Annotations, Maps, fun fold_qualified_annotation/2) end. -file("src/graded/internal/effects.gleam", 263). ?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) -> Dep_root = <<<>/binary, Package_name/binary>>, Spec_file = case graded@internal@config:read( <> ) of {ok, Cfg} -> erlang:element(3, Cfg); {error, _} -> graded@internal@config:default_spec_file(Package_name) end, load_spec_into_maps( Maps, <<<>/binary, Spec_file/binary>> ) end ). -file("src/graded/internal/effects.gleam", 320). ?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", 429). ?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", 449). ?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", 445). ?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", 408). ?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", 365). ?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", 463). ?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", 328). ?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", 35). ?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", 48). ?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}.