-module(caffeine_lang@frontend@validator). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "src/caffeine_lang/frontend/validator.gleam"). -export([validate_blueprints_file/1, validate_expects_file/1]). -export_type([validator_error/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. -type validator_error() :: {duplicate_extendable, binary()} | {undefined_extendable, binary(), binary(), list(binary())} | {duplicate_extends_reference, binary(), binary()} | {invalid_extendable_kind, binary(), binary(), binary()} | {undefined_type_alias, binary(), binary(), list(binary())} | {duplicate_type_alias, binary()} | {circular_type_alias, binary(), list(binary())} | {invalid_dict_key_type_alias, binary(), binary(), binary()} | {extendable_overshadowing, binary(), binary(), binary()} | {extendable_type_alias_name_collision, binary()}. -file("src/caffeine_lang/frontend/validator.gleam", 149). -spec validate_no_duplicates_loop( list(MFC), fun((MFC) -> binary()), fun((binary()) -> validator_error()), gleam@set:set(binary()) ) -> {ok, nil} | {error, validator_error()}. validate_no_duplicates_loop(Items, Get_name, Make_error, Seen) -> case Items of [] -> {ok, nil}; [First | Rest] -> Name = Get_name(First), gleam@bool:guard( gleam@set:contains(Seen, Name), {error, Make_error(Name)}, fun() -> validate_no_duplicates_loop( Rest, Get_name, Make_error, gleam@set:insert(Seen, Name) ) end ) end. -file("src/caffeine_lang/frontend/validator.gleam", 141). ?DOC(" Validates that no two items in a list share the same name.\n"). -spec validate_no_duplicates( list(MEY), fun((MEY) -> binary()), fun((binary()) -> validator_error()) ) -> {ok, nil} | {error, validator_error()}. validate_no_duplicates(Items, Get_name, Make_error) -> validate_no_duplicates_loop(Items, Get_name, Make_error, gleam@set:new()). -file("src/caffeine_lang/frontend/validator.gleam", 174). ?DOC(" Validates that no extendable shares a name with a type alias.\n"). -spec validate_no_extendable_type_alias_collision( list(caffeine_lang@frontend@ast:extendable()), list(caffeine_lang@frontend@ast:type_alias()) ) -> {ok, nil} | {error, validator_error()}. validate_no_extendable_type_alias_collision(Extendables, Type_aliases) -> Type_alias_names = begin _pipe = Type_aliases, _pipe@1 = gleam@list:map(_pipe, fun(Ta) -> erlang:element(2, Ta) end), gleam@set:from_list(_pipe@1) end, gleam@list:try_each( Extendables, fun(Ext) -> gleam@bool:guard( gleam@set:contains(Type_alias_names, erlang:element(2, Ext)), {error, {extendable_type_alias_name_collision, erlang:element(2, Ext)}}, fun() -> {ok, nil} end ) end ). -file("src/caffeine_lang/frontend/validator.gleam", 193). ?DOC(" Validates that all extendables in an expects file are Provides kind.\n"). -spec validate_extendables_are_provides( list(caffeine_lang@frontend@ast:extendable()) ) -> {ok, nil} | {error, validator_error()}. validate_extendables_are_provides(Extendables) -> case Extendables of [] -> {ok, nil}; [First | Rest] -> case erlang:element(3, First) of extendable_provides -> validate_extendables_are_provides(Rest); extendable_requires -> {error, {invalid_extendable_kind, erlang:element(2, First), <<"Provides"/utf8>>, <<"Requires"/utf8>>}} end end. -file("src/caffeine_lang/frontend/validator.gleam", 277). ?DOC(" Validates that all names in extends list exist as extendables.\n"). -spec validate_extends_exist(list(binary()), binary(), gleam@set:set(binary())) -> {ok, nil} | {error, validator_error()}. validate_extends_exist(Extends, Item_name, Extendable_names) -> case Extends of [] -> {ok, nil}; [First | Rest] -> gleam@bool:guard( not gleam@set:contains(Extendable_names, First), {error, {undefined_extendable, First, Item_name, gleam@set:to_list(Extendable_names)}}, fun() -> validate_extends_exist(Rest, Item_name, Extendable_names) end ) end. -file("src/caffeine_lang/frontend/validator.gleam", 306). -spec validate_no_duplicate_extends_loop( list(binary()), binary(), gleam@set:set(binary()) ) -> {ok, nil} | {error, validator_error()}. validate_no_duplicate_extends_loop(Extends, Item_name, Seen) -> case Extends of [] -> {ok, nil}; [First | Rest] -> gleam@bool:guard( gleam@set:contains(Seen, First), {error, {duplicate_extends_reference, First, Item_name}}, fun() -> validate_no_duplicate_extends_loop( Rest, Item_name, gleam@set:insert(Seen, First) ) end ) end. -file("src/caffeine_lang/frontend/validator.gleam", 299). ?DOC(" Validates that no extendable is referenced twice in the same extends list.\n"). -spec validate_no_duplicate_extends(list(binary()), binary()) -> {ok, nil} | {error, validator_error()}. validate_no_duplicate_extends(Extends, Item_name) -> validate_no_duplicate_extends_loop(Extends, Item_name, gleam@set:new()). -file("src/caffeine_lang/frontend/validator.gleam", 331). ?DOC(" Builds a map of extendable name to its field names for overshadowing checks.\n"). -spec build_extendable_field_map(list(caffeine_lang@frontend@ast:extendable())) -> gleam@dict:dict(binary(), gleam@set:set(binary())). build_extendable_field_map(Extendables) -> _pipe = Extendables, _pipe@3 = gleam@list:map( _pipe, fun(E) -> Field_names = begin _pipe@1 = erlang:element(2, erlang:element(4, E)), _pipe@2 = gleam@list:map( _pipe@1, fun(F) -> erlang:element(2, F) end ), gleam@set:from_list(_pipe@2) end, {erlang:element(2, E), Field_names} end ), maps:from_list(_pipe@3). -file("src/caffeine_lang/frontend/validator.gleam", 346). ?DOC(" Validates that an item's field sets don't overshadow fields from its extended extendables.\n"). -spec validate_no_overshadowing( binary(), list(binary()), list(gleam@set:set(binary())), gleam@dict:dict(binary(), gleam@set:set(binary())) ) -> {ok, nil} | {error, validator_error()}. validate_no_overshadowing(Item_name, Extends, Field_sets, Extendable_map) -> gleam@list:try_each( Extends, fun(Ext_name) -> case gleam_stdlib:map_get(Extendable_map, Ext_name) of {error, _} -> {ok, nil}; {ok, Ext_fields} -> gleam@list:try_each( Field_sets, fun(Item_fields) -> case begin _pipe = gleam@set:intersection( Item_fields, Ext_fields ), gleam@set:to_list(_pipe) end of [] -> {ok, nil}; [Field | _] -> {error, {extendable_overshadowing, Field, Item_name, Ext_name}} end end ) end end ). -file("src/caffeine_lang/frontend/validator.gleam", 213). ?DOC(" Validates extends references for blueprint items.\n"). -spec validate_blueprint_items_extends( list(caffeine_lang@frontend@ast:blueprint_item()), list(caffeine_lang@frontend@ast:extendable()) ) -> {ok, nil} | {error, validator_error()}. validate_blueprint_items_extends(Items, Extendables) -> Extendable_names = begin _pipe = Extendables, _pipe@1 = gleam@list:map(_pipe, fun(E) -> erlang:element(2, E) end), gleam@set:from_list(_pipe@1) end, Extendable_map = build_extendable_field_map(Extendables), gleam@list:try_each( Items, fun(Item) -> gleam@result:'try'( validate_extends_exist( erlang:element(3, Item), erlang:element(2, Item), Extendable_names ), fun(_) -> gleam@result:'try'( validate_no_duplicate_extends( erlang:element(3, Item), erlang:element(2, Item) ), fun(_) -> Requires_fields = begin _pipe@2 = erlang:element( 2, erlang:element(4, Item) ), _pipe@3 = gleam@list:map( _pipe@2, fun(F) -> erlang:element(2, F) end ), gleam@set:from_list(_pipe@3) end, Provides_fields = begin _pipe@4 = erlang:element( 2, erlang:element(5, Item) ), _pipe@5 = gleam@list:map( _pipe@4, fun(F@1) -> erlang:element(2, F@1) end ), gleam@set:from_list(_pipe@5) end, validate_no_overshadowing( erlang:element(2, Item), erlang:element(3, Item), [Requires_fields, Provides_fields], Extendable_map ) end ) end ) end ). -file("src/caffeine_lang/frontend/validator.gleam", 246). ?DOC(" Validates extends references for expect items.\n"). -spec validate_expect_items_extends( list(caffeine_lang@frontend@ast:expect_item()), list(caffeine_lang@frontend@ast:extendable()) ) -> {ok, nil} | {error, validator_error()}. validate_expect_items_extends(Items, Extendables) -> Extendable_names = begin _pipe = Extendables, _pipe@1 = gleam@list:map(_pipe, fun(E) -> erlang:element(2, E) end), gleam@set:from_list(_pipe@1) end, Extendable_map = build_extendable_field_map(Extendables), gleam@list:try_each( Items, fun(Item) -> gleam@result:'try'( validate_extends_exist( erlang:element(3, Item), erlang:element(2, Item), Extendable_names ), fun(_) -> gleam@result:'try'( validate_no_duplicate_extends( erlang:element(3, Item), erlang:element(2, Item) ), fun(_) -> Provides_fields = begin _pipe@2 = erlang:element( 2, erlang:element(4, Item) ), _pipe@3 = gleam@list:map( _pipe@2, fun(F) -> erlang:element(2, F) end ), gleam@set:from_list(_pipe@3) end, validate_no_overshadowing( erlang:element(2, Item), erlang:element(3, Item), [Provides_fields], Extendable_map ) end ) end ) end ). -file("src/caffeine_lang/frontend/validator.gleam", 376). ?DOC(" Builds a map of type alias name to its type for validation.\n"). -spec build_type_alias_map(list(caffeine_lang@frontend@ast:type_alias())) -> list({binary(), caffeine_lang@types:parsed_type()}). build_type_alias_map(Type_aliases) -> caffeine_lang@frontend@ast:build_type_alias_pairs(Type_aliases). -file("src/caffeine_lang/frontend/validator.gleam", 397). ?DOC( " Checks if a type contains a circular reference.\n" " Uses try_each_inner_parsed to handle the structural decomposition of compound types,\n" " with leaf-specific logic for ParsedPrimitive and ParsedTypeAliasRef.\n" ). -spec validate_type_alias_not_circular( binary(), caffeine_lang@types:parsed_type(), list({binary(), caffeine_lang@types:parsed_type()}), list(binary()) ) -> {ok, nil} | {error, validator_error()}. validate_type_alias_not_circular(Original_name, Typ, Type_alias_map, Visited) -> case Typ of {parsed_primitive, _} -> {ok, nil}; {parsed_type_alias_ref, Name} -> gleam@bool:guard( gleam@list:contains(Visited, Name), {error, {circular_type_alias, Original_name, Visited}}, fun() -> case gleam@list:key_find(Type_alias_map, Name) of {ok, Resolved} -> validate_type_alias_not_circular( Original_name, Resolved, Type_alias_map, [Name | Visited] ); {error, _} -> {ok, nil} end end ); _ -> caffeine_lang@types:try_each_inner_parsed( Typ, fun(Inner) -> validate_type_alias_not_circular( Original_name, Inner, Type_alias_map, Visited ) end ) end. -file("src/caffeine_lang/frontend/validator.gleam", 383). ?DOC(" Validates that no type alias has circular references.\n"). -spec validate_no_circular_type_aliases( list(caffeine_lang@frontend@ast:type_alias()) ) -> {ok, nil} | {error, validator_error()}. validate_no_circular_type_aliases(Type_aliases) -> Type_alias_map = build_type_alias_map(Type_aliases), gleam@list:try_each( Type_aliases, fun(Ta) -> validate_type_alias_not_circular( erlang:element(2, Ta), erlang:element(3, Ta), Type_alias_map, [erlang:element(2, Ta)] ) end ). -file("src/caffeine_lang/frontend/validator.gleam", 574). ?DOC(" Checks if a parsed type is String-based (String primitive or String refinement).\n"). -spec is_string_based_parsed_type(caffeine_lang@types:parsed_type()) -> boolean(). is_string_based_parsed_type(Typ) -> case Typ of {parsed_primitive, string} -> true; {parsed_refinement, {one_of, {parsed_primitive, string}, _}} -> true; _ -> false end. -file("src/caffeine_lang/frontend/validator.gleam", 535). ?DOC(" Validates that a Dict key type resolves to a String-based type.\n"). -spec validate_dict_key_type( caffeine_lang@types:parsed_type(), binary(), list({binary(), caffeine_lang@types:parsed_type()}) ) -> {ok, nil} | {error, validator_error()}. validate_dict_key_type(Key_type, Context_name, Type_alias_map) -> case Key_type of {parsed_primitive, string} -> {ok, nil}; {parsed_type_alias_ref, Alias_name} -> case gleam@list:key_find(Type_alias_map, Alias_name) of {ok, Resolved} -> gleam@bool:guard( is_string_based_parsed_type(Resolved), {ok, nil}, fun() -> {error, {invalid_dict_key_type_alias, Alias_name, caffeine_lang@types:parsed_type_to_string( Resolved ), Context_name}} end ); {error, _} -> {ok, nil} end; {parsed_refinement, {one_of, {parsed_primitive, string}, _}} -> {ok, nil}; _ -> {error, {invalid_dict_key_type_alias, <<"inline"/utf8>>, caffeine_lang@types:parsed_type_to_string(Key_type), Context_name}} end. -file("src/caffeine_lang/frontend/validator.gleam", 486). ?DOC( " Validates that all ParsedTypeAliasRef in a type are defined.\n" " Uses try_each_inner_parsed to handle the structural decomposition of compound types.\n" " Adds special Dict key validation for collection types.\n" ). -spec validate_type_refs( caffeine_lang@types:parsed_type(), binary(), gleam@set:set(binary()), list({binary(), caffeine_lang@types:parsed_type()}) ) -> {ok, nil} | {error, validator_error()}. validate_type_refs(Typ, Context_name, Type_alias_names, Type_alias_map) -> case Typ of {parsed_primitive, _} -> {ok, nil}; {parsed_type_alias_ref, Name} -> gleam@bool:guard( gleam@set:contains(Type_alias_names, Name), {ok, nil}, fun() -> {error, {undefined_type_alias, Name, Context_name, gleam@set:to_list(Type_alias_names)}} end ); {parsed_collection, {dict, Key, _}} -> gleam@result:'try'( validate_dict_key_type(Key, Context_name, Type_alias_map), fun(_) -> caffeine_lang@types:try_each_inner_parsed( Typ, fun(Inner) -> validate_type_refs( Inner, Context_name, Type_alias_names, Type_alias_map ) end ) end ); _ -> caffeine_lang@types:try_each_inner_parsed( Typ, fun(Inner@1) -> validate_type_refs( Inner@1, Context_name, Type_alias_names, Type_alias_map ) end ) end. -file("src/caffeine_lang/frontend/validator.gleam", 468). ?DOC(" Validates type alias references in a list of fields.\n"). -spec validate_fields_type_refs( list(caffeine_lang@frontend@ast:field()), binary(), gleam@set:set(binary()), list({binary(), caffeine_lang@types:parsed_type()}) ) -> {ok, nil} | {error, validator_error()}. validate_fields_type_refs( Fields, Context_name, Type_alias_names, Type_alias_map ) -> gleam@list:try_each(Fields, fun(Field) -> case erlang:element(3, Field) of {type_value, Typ} -> validate_type_refs( Typ, Context_name, Type_alias_names, Type_alias_map ); {literal_value, _} -> {ok, nil} end end). -file("src/caffeine_lang/frontend/validator.gleam", 436). ?DOC(" Validates type alias references in extendables.\n"). -spec validate_extendables_type_refs( list(caffeine_lang@frontend@ast:extendable()), gleam@set:set(binary()), list({binary(), caffeine_lang@types:parsed_type()}) ) -> {ok, nil} | {error, validator_error()}. validate_extendables_type_refs(Extendables, Type_alias_names, Type_alias_map) -> gleam@list:try_each( Extendables, fun(Ext) -> validate_fields_type_refs( erlang:element(2, erlang:element(4, Ext)), erlang:element(2, Ext), Type_alias_names, Type_alias_map ) end ). -file("src/caffeine_lang/frontend/validator.gleam", 452). ?DOC(" Validates type alias references in blueprint items.\n"). -spec validate_blueprint_items_type_refs( list(caffeine_lang@frontend@ast:blueprint_item()), gleam@set:set(binary()), list({binary(), caffeine_lang@types:parsed_type()}) ) -> {ok, nil} | {error, validator_error()}. validate_blueprint_items_type_refs(Items, Type_alias_names, Type_alias_map) -> gleam@list:try_each( Items, fun(Item) -> validate_fields_type_refs( erlang:element(2, erlang:element(4, Item)), erlang:element(2, Item), Type_alias_names, Type_alias_map ) end ). -file("src/caffeine_lang/frontend/validator.gleam", 587). ?DOC(" Converts a single-error Result to a list of errors (empty on Ok).\n"). -spec errors_to_list({ok, nil} | {error, validator_error()}) -> list(validator_error()). errors_to_list(Result) -> case Result of {ok, _} -> []; {error, Err} -> [Err] end. -file("src/caffeine_lang/frontend/validator.gleam", 595). ?DOC(" Collects errors from a list of independent validation results.\n"). -spec collect_errors(list({ok, nil} | {error, validator_error()})) -> list(validator_error()). collect_errors(Results) -> _pipe = Results, gleam@list:flat_map(_pipe, fun errors_to_list/1). -file("src/caffeine_lang/frontend/validator.gleam", 603). ?DOC( " Guards against accumulated errors. If errors is non-empty, returns them;\n" " otherwise continues with the provided callback.\n" ). -spec guard_errors( list(validator_error()), fun(() -> {ok, MIG} | {error, list(validator_error())}) ) -> {ok, MIG} | {error, list(validator_error())}. guard_errors(Errors, Otherwise) -> case Errors of [] -> Otherwise(); _ -> {error, Errors} end. -file("src/caffeine_lang/frontend/validator.gleam", 53). ?DOC(false). -spec validate_blueprints_file(caffeine_lang@frontend@ast:blueprints_file()) -> {ok, caffeine_lang@frontend@ast:blueprints_file()} | {error, list(validator_error())}. validate_blueprints_file(File) -> Type_aliases = erlang:element(2, File), Extendables = erlang:element(3, File), Items = begin _pipe = erlang:element(4, File), gleam@list:flat_map(_pipe, fun(Block) -> erlang:element(3, Block) end) end, Type_alias_errors = begin _pipe@1 = validate_no_duplicates( Type_aliases, fun(Ta) -> erlang:element(2, Ta) end, fun(Field@0) -> {duplicate_type_alias, Field@0} end ), _pipe@2 = gleam@result:'try'( _pipe@1, fun(_) -> validate_no_circular_type_aliases(Type_aliases) end ), errors_to_list(_pipe@2) end, Extendable_errors = collect_errors( [validate_no_duplicates( Extendables, fun(E) -> erlang:element(2, E) end, fun(Field@0) -> {duplicate_extendable, Field@0} end ), validate_no_extendable_type_alias_collision( Extendables, Type_aliases )] ), Structural_errors = lists:append(Type_alias_errors, Extendable_errors), guard_errors( Structural_errors, fun() -> Type_alias_names = begin _pipe@3 = Type_aliases, _pipe@4 = gleam@list:map( _pipe@3, fun(Ta@1) -> erlang:element(2, Ta@1) end ), gleam@set:from_list(_pipe@4) end, Type_alias_map = build_type_alias_map(Type_aliases), Type_ref_errors = collect_errors( [validate_extendables_type_refs( Extendables, Type_alias_names, Type_alias_map ), validate_blueprint_items_type_refs( Items, Type_alias_names, Type_alias_map )] ), Extends_errors = begin _pipe@5 = validate_blueprint_items_extends(Items, Extendables), errors_to_list(_pipe@5) end, Dependent_errors = lists:append(Type_ref_errors, Extends_errors), guard_errors(Dependent_errors, fun() -> {ok, File} end) end ). -file("src/caffeine_lang/frontend/validator.gleam", 116). ?DOC(false). -spec validate_expects_file(caffeine_lang@frontend@ast:expects_file()) -> {ok, caffeine_lang@frontend@ast:expects_file()} | {error, list(validator_error())}. validate_expects_file(File) -> Extendables = erlang:element(2, File), Items = begin _pipe = erlang:element(3, File), gleam@list:flat_map(_pipe, fun(Block) -> erlang:element(3, Block) end) end, Structural_errors = collect_errors( [validate_no_duplicates( Extendables, fun(E) -> erlang:element(2, E) end, fun(Field@0) -> {duplicate_extendable, Field@0} end ), validate_extendables_are_provides(Extendables)] ), guard_errors( Structural_errors, fun() -> Extends_errors = begin _pipe@1 = validate_expect_items_extends(Items, Extendables), errors_to_list(_pipe@1) end, guard_errors(Extends_errors, fun() -> {ok, File} end) end ).