Current section

Files

Jump to
caffeine_lang src caffeine_lang@frontend@lowering.erl
Raw

src/caffeine_lang@frontend@lowering.erl

-module(caffeine_lang@frontend@lowering).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/caffeine_lang/frontend/lowering.gleam").
-export([literal_to_value/1, lower_expectations/1, lower_blueprints/1]).
-if(?OTP_RELEASE >= 27).
-define(MODULEDOC(Str), -moduledoc(Str)).
-define(DOC(Str), -doc(Str)).
-else.
-define(MODULEDOC(Str), -compile([])).
-define(DOC(Str), -compile([])).
-endif.
-file("src/caffeine_lang/frontend/lowering.gleam", 52).
?DOC(" Builds a map of extendable name to extendable for quick lookup.\n").
-spec build_extendable_map(list(caffeine_lang@frontend@ast:extendable())) -> gleam@dict:dict(binary(), caffeine_lang@frontend@ast:extendable()).
build_extendable_map(Extendables) ->
_pipe = Extendables,
_pipe@1 = gleam@list:map(_pipe, fun(E) -> {erlang:element(2, E), E} end),
maps:from_list(_pipe@1).
-file("src/caffeine_lang/frontend/lowering.gleam", 61).
?DOC(" Builds a map of type alias name to its parsed type for quick lookup.\n").
-spec build_type_alias_map(list(caffeine_lang@frontend@ast:type_alias())) -> gleam@dict:dict(binary(), caffeine_lang@types:parsed_type()).
build_type_alias_map(Type_aliases) ->
_pipe = caffeine_lang@frontend@ast:build_type_alias_pairs(Type_aliases),
maps:from_list(_pipe).
-file("src/caffeine_lang/frontend/lowering.gleam", 92).
?DOC(" Converts a ParsedArtifactRef to an ArtifactType (infallible).\n").
-spec lower_artifact_ref(caffeine_lang@frontend@ast:parsed_artifact_ref()) -> caffeine_lang@linker@artifacts:artifact_type().
lower_artifact_ref(Ref) ->
case Ref of
parsed_s_l_o ->
s_l_o;
parsed_dependency_relations ->
dependency_relations
end.
-file("src/caffeine_lang/frontend/lowering.gleam", 112).
?DOC(" Collects fields from extended extendables matching a given kind.\n").
-spec collect_extended_fields(
list(binary()),
gleam@dict:dict(binary(), caffeine_lang@frontend@ast:extendable()),
caffeine_lang@frontend@ast:extendable_kind()
) -> list(caffeine_lang@frontend@ast:field()).
collect_extended_fields(Extends, Extendables, Kind) ->
_pipe = Extends,
gleam@list:flat_map(
_pipe,
fun(Name) -> case gleam_stdlib:map_get(Extendables, Name) of
{ok, Ext} when erlang:element(3, Ext) =:= Kind ->
erlang:element(2, erlang:element(4, Ext));
_ ->
[]
end end
).
-file("src/caffeine_lang/frontend/lowering.gleam", 164).
?DOC(
" Removes duplicate field names, keeping the last occurrence (allows overrides).\n"
" Returns fields sorted by name for consistent output.\n"
).
-spec dedupe_fields(list(caffeine_lang@frontend@ast:field())) -> list(caffeine_lang@frontend@ast:field()).
dedupe_fields(Fields) ->
_pipe = Fields,
_pipe@1 = gleam@list:fold(
_pipe,
maps:new(),
fun(Acc, Field) ->
gleam@dict:insert(Acc, erlang:element(2, Field), Field)
end
),
_pipe@2 = maps:to_list(_pipe@1),
_pipe@3 = gleam@list:sort(
_pipe@2,
fun(A, B) ->
gleam@string:compare(erlang:element(1, A), erlang:element(1, B))
end
),
gleam@list:map(_pipe@3, fun(Pair) -> erlang:element(2, Pair) end).
-file("src/caffeine_lang/frontend/lowering.gleam", 128).
?DOC(
" Merges extended fields into a blueprint item's requires and provides.\n"
" Order: extended extendables left-to-right, then item's own fields (can override).\n"
).
-spec merge_blueprint_extends(
caffeine_lang@frontend@ast:blueprint_item(),
gleam@dict:dict(binary(), caffeine_lang@frontend@ast:extendable())
) -> {caffeine_lang@frontend@ast:struct(), caffeine_lang@frontend@ast:struct()}.
merge_blueprint_extends(Item, Extendables) ->
Requires_fields = begin
_pipe = collect_extended_fields(
erlang:element(3, Item),
Extendables,
extendable_requires
),
_pipe@1 = lists:append(
_pipe,
erlang:element(2, erlang:element(4, Item))
),
dedupe_fields(_pipe@1)
end,
Provides_fields = begin
_pipe@2 = collect_extended_fields(
erlang:element(3, Item),
Extendables,
extendable_provides
),
_pipe@3 = lists:append(
_pipe@2,
erlang:element(2, erlang:element(5, Item))
),
dedupe_fields(_pipe@3)
end,
{{struct, Requires_fields, []}, {struct, Provides_fields, []}}.
-file("src/caffeine_lang/frontend/lowering.gleam", 150).
?DOC(
" Merges extended fields into an expect item's provides.\n"
" Order: extended extendables left-to-right, then item's own fields (can override).\n"
).
-spec merge_expect_extends(
caffeine_lang@frontend@ast:expect_item(),
gleam@dict:dict(binary(), caffeine_lang@frontend@ast:extendable())
) -> caffeine_lang@frontend@ast:struct().
merge_expect_extends(Item, Extendables) ->
Provides_fields = begin
_pipe = collect_extended_fields(
erlang:element(3, Item),
Extendables,
extendable_provides
),
_pipe@1 = lists:append(
_pipe,
erlang:element(2, erlang:element(4, Item))
),
dedupe_fields(_pipe@1)
end,
{struct, Provides_fields, []}.
-file("src/caffeine_lang/frontend/lowering.gleam", 302).
-spec transform_template_vars_loop(binary(), binary()) -> binary().
transform_template_vars_loop(Remaining, Acc) ->
case gleam@string:split_once(Remaining, <<"$"/utf8>>) of
{ok, {Before, After}} ->
case gleam_stdlib:string_starts_with(After, <<"$"/utf8>>) of
true ->
transform_template_vars_loop(
gleam@string:drop_start(After, 1),
<<<<Acc/binary, Before/binary>>/binary, "$$"/utf8>>
);
false ->
case gleam@string:split_once(After, <<"$"/utf8>>) of
{ok, {Var_content, Rest}} ->
Transformed = gleam@string:replace(
Var_content,
<<".not"/utf8>>,
<<":not"/utf8>>
),
transform_template_vars_loop(
Rest,
<<<<<<<<Acc/binary, Before/binary>>/binary,
"$$"/utf8>>/binary,
Transformed/binary>>/binary,
"$$"/utf8>>
);
{error, nil} ->
<<<<<<Acc/binary, Before/binary>>/binary, "$"/utf8>>/binary,
After/binary>>
end
end;
{error, nil} ->
<<Acc/binary, Remaining/binary>>
end.
-file("src/caffeine_lang/frontend/lowering.gleam", 298).
?DOC(
" Transforms template variables from $var->attr$ to $$var->attr$$ format.\n"
" Also handles $var->attr.not$ to $$var->attr:not$$ format.\n"
).
-spec transform_template_vars(binary()) -> binary().
transform_template_vars(S) ->
transform_template_vars_loop(S, <<""/utf8>>).
-file("src/caffeine_lang/frontend/lowering.gleam", 273).
?DOC(false).
-spec literal_to_value(caffeine_lang@frontend@ast:literal()) -> caffeine_lang@value:value().
literal_to_value(Lit) ->
case Lit of
{literal_string, S} ->
{string_value, transform_template_vars(S)};
{literal_integer, I} ->
{int_value, I};
{literal_float, F} ->
{float_value, F};
{literal_percentage, F@1} ->
{float_value, F@1};
literal_true ->
{bool_value, true};
literal_false ->
{bool_value, false};
{literal_list, Elements} ->
{list_value, gleam@list:map(Elements, fun literal_to_value/1)};
{literal_struct, Fields, _} ->
_pipe = Fields,
_pipe@1 = gleam@list:filter_map(
_pipe,
fun(Field) -> case erlang:element(3, Field) of
{literal_value, Inner} ->
{ok,
{erlang:element(2, Field),
literal_to_value(Inner)}};
{type_value, _} ->
{error, nil}
end end
),
_pipe@2 = maps:from_list(_pipe@1),
{dict_value, _pipe@2}
end.
-file("src/caffeine_lang/frontend/lowering.gleam", 260).
?DOC(" Converts a struct's literal-valued fields to an inputs dict.\n").
-spec struct_to_inputs(caffeine_lang@frontend@ast:struct()) -> gleam@dict:dict(binary(), caffeine_lang@value:value()).
struct_to_inputs(S) ->
_pipe = erlang:element(2, S),
_pipe@1 = gleam@list:filter_map(
_pipe,
fun(Field) -> case erlang:element(3, Field) of
{literal_value, Lit} ->
{ok, {erlang:element(2, Field), literal_to_value(Lit)}};
{type_value, _} ->
{error, nil}
end end
),
maps:from_list(_pipe@1).
-file("src/caffeine_lang/frontend/lowering.gleam", 100).
?DOC(" Generates a single expectation from an AST item.\n").
-spec generate_expect_item(
caffeine_lang@frontend@ast:expect_item(),
binary(),
gleam@dict:dict(binary(), caffeine_lang@frontend@ast:extendable())
) -> caffeine_lang@linker@expectations:expectation().
generate_expect_item(Item, Blueprint, Extendables) ->
Merged_provides = merge_expect_extends(Item, Extendables),
Inputs = struct_to_inputs(Merged_provides),
{expectation, erlang:element(2, Item), Blueprint, Inputs}.
-file("src/caffeine_lang/frontend/lowering.gleam", 39).
?DOC(false).
-spec lower_expectations(caffeine_lang@frontend@ast:expects_file()) -> list(caffeine_lang@linker@expectations:expectation()).
lower_expectations(File) ->
Extendables = build_extendable_map(erlang:element(2, File)),
_pipe = erlang:element(3, File),
gleam@list:flat_map(_pipe, fun(Block) -> _pipe@1 = erlang:element(3, Block),
gleam@list:map(
_pipe@1,
fun(Item) ->
generate_expect_item(
Item,
erlang:element(2, Block),
Extendables
)
end
) end).
-file("src/caffeine_lang/frontend/lowering.gleam", 221).
?DOC(" Resolves inner types of a collection.\n").
-spec resolve_collection(
caffeine_lang@types:collection_types(caffeine_lang@types:parsed_type()),
gleam@dict:dict(binary(), caffeine_lang@types:parsed_type())
) -> caffeine_lang@types:collection_types(caffeine_lang@types:accepted_types()).
resolve_collection(Collection, Aliases) ->
case Collection of
{list, Inner} ->
{list, resolve_type_aliases(Inner, Aliases)};
{dict, Key, Value} ->
{dict,
resolve_type_aliases(Key, Aliases),
resolve_type_aliases(Value, Aliases)}
end.
-file("src/caffeine_lang/frontend/lowering.gleam", 195).
?DOC(
" Resolves all ParsedTypeAliasRef instances, converting ParsedType to AcceptedTypes.\n"
" This is the resolution boundary where parsed types become fully resolved types.\n"
).
-spec resolve_type_aliases(
caffeine_lang@types:parsed_type(),
gleam@dict:dict(binary(), caffeine_lang@types:parsed_type())
) -> caffeine_lang@types:accepted_types().
resolve_type_aliases(T, Aliases) ->
case T of
{parsed_primitive, P} ->
{primitive_type, P};
{parsed_type_alias_ref, Name} ->
case gleam_stdlib:map_get(Aliases, Name) of
{ok, Resolved} ->
resolve_type_aliases(Resolved, Aliases);
{error, _} ->
{primitive_type, string}
end;
{parsed_collection, Collection} ->
{collection_type, resolve_collection(Collection, Aliases)};
{parsed_modifier, Modifier} ->
{modifier_type, resolve_modifier(Modifier, Aliases)};
{parsed_refinement, Refinement} ->
{refinement_type, resolve_refinement(Refinement, Aliases)};
{parsed_record, Fields} ->
{record_type,
gleam@dict:map_values(
Fields,
fun(_, V) -> resolve_type_aliases(V, Aliases) end
)}
end.
-file("src/caffeine_lang/frontend/lowering.gleam", 176).
?DOC(
" Converts a struct's type-valued fields to a params dict.\n"
" Resolves type alias references before storing.\n"
).
-spec struct_to_params(
caffeine_lang@frontend@ast:struct(),
gleam@dict:dict(binary(), caffeine_lang@types:parsed_type())
) -> gleam@dict:dict(binary(), caffeine_lang@types:accepted_types()).
struct_to_params(S, Type_aliases) ->
_pipe = erlang:element(2, S),
_pipe@1 = gleam@list:filter_map(
_pipe,
fun(Field) -> case erlang:element(3, Field) of
{type_value, T} ->
Resolved = resolve_type_aliases(T, Type_aliases),
{ok, {erlang:element(2, Field), Resolved}};
{literal_value, _} ->
{error, nil}
end end
),
maps:from_list(_pipe@1).
-file("src/caffeine_lang/frontend/lowering.gleam", 69).
?DOC(" Generates a single blueprint from an AST item.\n").
-spec generate_blueprint_item(
caffeine_lang@frontend@ast:blueprint_item(),
list(caffeine_lang@frontend@ast:parsed_artifact_ref()),
gleam@dict:dict(binary(), caffeine_lang@frontend@ast:extendable()),
gleam@dict:dict(binary(), caffeine_lang@types:parsed_type())
) -> caffeine_lang@linker@blueprints:blueprint().
generate_blueprint_item(Item, Parsed_artifacts, Extendables, Type_aliases) ->
Artifact_refs = gleam@list:map(Parsed_artifacts, fun lower_artifact_ref/1),
{Merged_requires, Merged_provides} = merge_blueprint_extends(
Item,
Extendables
),
Params = struct_to_params(Merged_requires, Type_aliases),
Inputs = struct_to_inputs(Merged_provides),
{blueprint, erlang:element(2, Item), Artifact_refs, Params, Inputs}.
-file("src/caffeine_lang/frontend/lowering.gleam", 24).
?DOC(false).
-spec lower_blueprints(caffeine_lang@frontend@ast:blueprints_file()) -> list(caffeine_lang@linker@blueprints:blueprint()).
lower_blueprints(File) ->
Type_aliases = build_type_alias_map(erlang:element(2, File)),
Extendables = build_extendable_map(erlang:element(3, File)),
_pipe = erlang:element(4, File),
gleam@list:flat_map(_pipe, fun(Block) -> _pipe@1 = erlang:element(3, Block),
gleam@list:map(
_pipe@1,
fun(Item) ->
generate_blueprint_item(
Item,
erlang:element(2, Block),
Extendables,
Type_aliases
)
end
) end).
-file("src/caffeine_lang/frontend/lowering.gleam", 236).
?DOC(" Resolves inner types of a modifier.\n").
-spec resolve_modifier(
caffeine_lang@types:modifier_types(caffeine_lang@types:parsed_type()),
gleam@dict:dict(binary(), caffeine_lang@types:parsed_type())
) -> caffeine_lang@types:modifier_types(caffeine_lang@types:accepted_types()).
resolve_modifier(Modifier, Aliases) ->
case Modifier of
{optional, Inner} ->
{optional, resolve_type_aliases(Inner, Aliases)};
{defaulted, Inner@1, Default} ->
{defaulted, resolve_type_aliases(Inner@1, Aliases), Default}
end.
-file("src/caffeine_lang/frontend/lowering.gleam", 248).
?DOC(" Resolves inner types of a refinement.\n").
-spec resolve_refinement(
caffeine_lang@types:refinement_types(caffeine_lang@types:parsed_type()),
gleam@dict:dict(binary(), caffeine_lang@types:parsed_type())
) -> caffeine_lang@types:refinement_types(caffeine_lang@types:accepted_types()).
resolve_refinement(Refinement, Aliases) ->
case Refinement of
{one_of, Inner, Values} ->
{one_of, resolve_type_aliases(Inner, Aliases), Values};
{inclusive_range, Inner@1, Low, High} ->
{inclusive_range, resolve_type_aliases(Inner@1, Aliases), Low, High}
end.