Current section

Files

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

src/caffeine_lang@frontend@generator.erl

-module(caffeine_lang@frontend@generator).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/caffeine_lang/frontend/generator.gleam").
-export([generate_expects_json/1, generate_blueprints_json/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/generator.gleam", 57).
?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/generator.gleam", 66).
?DOC(" Builds a map of type alias name to its resolved type for quick lookup.\n").
-spec build_type_alias_map(list(caffeine_lang@frontend@ast:type_alias())) -> gleam@dict:dict(binary(), caffeine_lang@common@accepted_types:accepted_types()).
build_type_alias_map(Type_aliases) ->
_pipe = Type_aliases,
_pipe@1 = gleam@list:map(
_pipe,
fun(Ta) -> {erlang:element(2, Ta), erlang:element(3, Ta)} end
),
maps:from_list(_pipe@1).
-file("src/caffeine_lang/frontend/generator.gleam", 179).
?DOC(
" Removes duplicate field names, keeping the last occurrence (allows overrides).\n"
" Returns fields sorted by name for consistent JSON 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/generator.gleam", 120).
?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 = erlang:element(3, Item),
_pipe@1 = gleam@list:flat_map(
_pipe,
fun(Name) -> case gleam_stdlib:map_get(Extendables, Name) of
{ok, Ext} ->
case erlang:element(3, Ext) of
extendable_requires ->
erlang:element(2, erlang:element(4, Ext));
extendable_provides ->
[]
end;
{error, _} ->
[]
end end
),
_pipe@2 = lists:append(
_pipe@1,
erlang:element(2, erlang:element(4, Item))
),
dedupe_fields(_pipe@2)
end,
Provides_fields = begin
_pipe@3 = erlang:element(3, Item),
_pipe@4 = gleam@list:flat_map(
_pipe@3,
fun(Name@1) -> case gleam_stdlib:map_get(Extendables, Name@1) of
{ok, Ext@1} ->
case erlang:element(3, Ext@1) of
extendable_provides ->
erlang:element(2, erlang:element(4, Ext@1));
extendable_requires ->
[]
end;
{error, _} ->
[]
end end
),
_pipe@5 = lists:append(
_pipe@4,
erlang:element(2, erlang:element(5, Item))
),
dedupe_fields(_pipe@5)
end,
{{struct, Requires_fields}, {struct, Provides_fields}}.
-file("src/caffeine_lang/frontend/generator.gleam", 159).
?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 = erlang:element(3, Item),
_pipe@1 = gleam@list:flat_map(
_pipe,
fun(Name) -> case gleam_stdlib:map_get(Extendables, Name) of
{ok, Ext} ->
erlang:element(2, erlang:element(4, Ext));
{error, _} ->
[]
end end
),
_pipe@2 = lists:append(
_pipe@1,
erlang:element(2, erlang:element(4, Item))
),
dedupe_fields(_pipe@2)
end,
{struct, Provides_fields}.
-file("src/caffeine_lang/frontend/generator.gleam", 332).
-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/generator.gleam", 328).
?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/generator.gleam", 313).
?DOC(" Converts a literal struct's fields to a JSON object.\n").
-spec literal_struct_to_json(list(caffeine_lang@frontend@ast:field())) -> gleam@json:json().
literal_struct_to_json(Fields) ->
_pipe = Fields,
_pipe@1 = gleam@list:sort(
_pipe,
fun(A, B) ->
gleam@string:compare(erlang:element(2, A), erlang:element(2, B))
end
),
_pipe@2 = gleam@list:map(
_pipe@1,
fun(Field) ->
Json_value = case erlang:element(3, Field) of
{literal_value, Lit} ->
literal_to_json(Lit);
{type_value, _} ->
gleam@json:null()
end,
{erlang:element(2, Field), Json_value}
end
),
gleam@json:object(_pipe@2).
-file("src/caffeine_lang/frontend/generator.gleam", 299).
?DOC(" Converts a literal to a JSON value.\n").
-spec literal_to_json(caffeine_lang@frontend@ast:literal()) -> gleam@json:json().
literal_to_json(Lit) ->
case Lit of
{literal_string, S} ->
gleam@json:string(transform_template_vars(S));
{literal_integer, I} ->
gleam@json:int(I);
{literal_float, F} ->
gleam@json:float(F);
literal_true ->
gleam@json:bool(true);
literal_false ->
gleam@json:bool(false);
{literal_list, Elements} ->
gleam@json:array(Elements, fun(E) -> literal_to_json(E) end);
{literal_struct, Fields} ->
literal_struct_to_json(Fields)
end.
-file("src/caffeine_lang/frontend/generator.gleam", 285).
?DOC(" Converts a struct with literal values to a JSON inputs object.\n").
-spec struct_to_inputs_json(caffeine_lang@frontend@ast:struct()) -> gleam@json:json().
struct_to_inputs_json(S) ->
_pipe = erlang:element(2, S),
_pipe@1 = gleam@list:sort(
_pipe,
fun(A, B) ->
gleam@string:compare(erlang:element(2, A), erlang:element(2, B))
end
),
_pipe@2 = gleam@list:map(
_pipe@1,
fun(Field) ->
Json_value = case erlang:element(3, Field) of
{literal_value, Lit} ->
literal_to_json(Lit);
{type_value, _} ->
gleam@json:null()
end,
{erlang:element(2, Field), Json_value}
end
),
gleam@json:object(_pipe@2).
-file("src/caffeine_lang/frontend/generator.gleam", 100).
?DOC(" Generates JSON for a single expect item.\n").
-spec generate_expect_item_json(
caffeine_lang@frontend@ast:expect_item(),
binary(),
gleam@dict:dict(binary(), caffeine_lang@frontend@ast:extendable())
) -> gleam@json:json().
generate_expect_item_json(Item, Blueprint, Extendables) ->
Merged_provides = merge_expect_extends(Item, Extendables),
Inputs = struct_to_inputs_json(Merged_provides),
gleam@json:object(
[{<<"name"/utf8>>, gleam@json:string(erlang:element(2, Item))},
{<<"blueprint_ref"/utf8>>, gleam@json:string(Blueprint)},
{<<"inputs"/utf8>>, Inputs}]
).
-file("src/caffeine_lang/frontend/generator.gleam", 41).
?DOC(false).
-spec generate_expects_json(caffeine_lang@frontend@ast:expects_file()) -> gleam@json:json().
generate_expects_json(File) ->
Extendables = build_extendable_map(erlang:element(2, File)),
Expectations = begin
_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_json(
Item,
erlang:element(2, Block),
Extendables
)
end
) end
)
end,
gleam@json:object(
[{<<"expectations"/utf8>>,
gleam@json:array(Expectations, fun(X) -> X end)}]
).
-file("src/caffeine_lang/frontend/generator.gleam", 239).
?DOC(" Resolves type aliases in collection types.\n").
-spec resolve_collection_aliases(
caffeine_lang@common@collection_types:collection_types(caffeine_lang@common@accepted_types:accepted_types()),
gleam@dict:dict(binary(), caffeine_lang@common@accepted_types:accepted_types())
) -> caffeine_lang@common@collection_types:collection_types(caffeine_lang@common@accepted_types:accepted_types()).
resolve_collection_aliases(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/generator.gleam", 212).
?DOC(
" Resolves all TypeAliasRef instances in a type by looking them up in the alias map.\n"
" Recursively resolves nested types (in collections, modifiers, refinements).\n"
).
-spec resolve_type_aliases(
caffeine_lang@common@accepted_types:accepted_types(),
gleam@dict:dict(binary(), caffeine_lang@common@accepted_types:accepted_types())
) -> caffeine_lang@common@accepted_types:accepted_types().
resolve_type_aliases(T, Aliases) ->
case T of
{primitive_type, _} ->
T;
{type_alias_ref, Name} ->
case gleam_stdlib:map_get(Aliases, Name) of
{ok, Resolved} ->
resolve_type_aliases(Resolved, Aliases);
{error, _} ->
T
end;
{collection_type, Collection} ->
{collection_type, resolve_collection_aliases(Collection, Aliases)};
{modifier_type, Modifier} ->
{modifier_type, resolve_modifier_aliases(Modifier, Aliases)};
{refinement_type, Refinement} ->
{refinement_type, resolve_refinement_aliases(Refinement, Aliases)}
end.
-file("src/caffeine_lang/frontend/generator.gleam", 191).
?DOC(
" Converts a struct with type values to a JSON params object.\n"
" Resolves type alias references before converting to strings.\n"
).
-spec struct_to_params_json(
caffeine_lang@frontend@ast:struct(),
gleam@dict:dict(binary(), caffeine_lang@common@accepted_types:accepted_types())
) -> gleam@json:json().
struct_to_params_json(S, Type_aliases) ->
_pipe = erlang:element(2, S),
_pipe@1 = gleam@list:sort(
_pipe,
fun(A, B) ->
gleam@string:compare(erlang:element(2, A), erlang:element(2, B))
end
),
_pipe@2 = gleam@list:map(
_pipe@1,
fun(Field) ->
Type_string = case erlang:element(3, Field) of
{type_value, T} ->
Resolved = resolve_type_aliases(T, Type_aliases),
caffeine_lang@common@accepted_types:accepted_type_to_string(
Resolved
);
{literal_value, _} ->
<<""/utf8>>
end,
{erlang:element(2, Field), gleam@json:string(Type_string)}
end
),
gleam@json:object(_pipe@2).
-file("src/caffeine_lang/frontend/generator.gleam", 75).
?DOC(" Generates JSON for a single blueprint item.\n").
-spec generate_blueprint_item_json(
caffeine_lang@frontend@ast:blueprint_item(),
list(binary()),
gleam@dict:dict(binary(), caffeine_lang@frontend@ast:extendable()),
gleam@dict:dict(binary(), caffeine_lang@common@accepted_types:accepted_types())
) -> gleam@json:json().
generate_blueprint_item_json(Item, Artifacts, Extendables, Type_aliases) ->
{Merged_requires, Merged_provides} = merge_blueprint_extends(
Item,
Extendables
),
Params = struct_to_params_json(Merged_requires, Type_aliases),
Inputs = struct_to_inputs_json(Merged_provides),
gleam@json:object(
[{<<"name"/utf8>>, gleam@json:string(erlang:element(2, Item))},
{<<"artifact_refs"/utf8>>,
gleam@json:array(Artifacts, fun gleam@json:string/1)},
{<<"params"/utf8>>, Params},
{<<"inputs"/utf8>>, Inputs}]
).
-file("src/caffeine_lang/frontend/generator.gleam", 18).
?DOC(false).
-spec generate_blueprints_json(caffeine_lang@frontend@ast:blueprints_file()) -> gleam@json:json().
generate_blueprints_json(File) ->
Type_aliases = build_type_alias_map(erlang:element(2, File)),
Extendables = build_extendable_map(erlang:element(3, File)),
Blueprints = begin
_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_json(
Item,
erlang:element(2, Block),
Extendables,
Type_aliases
)
end
) end
)
end,
gleam@json:object(
[{<<"blueprints"/utf8>>, gleam@json:array(Blueprints, fun(X) -> X end)}]
).
-file("src/caffeine_lang/frontend/generator.gleam", 255).
?DOC(" Resolves type aliases in modifier types.\n").
-spec resolve_modifier_aliases(
caffeine_lang@common@modifier_types:modifier_types(caffeine_lang@common@accepted_types:accepted_types()),
gleam@dict:dict(binary(), caffeine_lang@common@accepted_types:accepted_types())
) -> caffeine_lang@common@modifier_types:modifier_types(caffeine_lang@common@accepted_types:accepted_types()).
resolve_modifier_aliases(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/generator.gleam", 268).
?DOC(" Resolves type aliases in refinement types.\n").
-spec resolve_refinement_aliases(
caffeine_lang@common@refinement_types:refinement_types(caffeine_lang@common@accepted_types:accepted_types()),
gleam@dict:dict(binary(), caffeine_lang@common@accepted_types:accepted_types())
) -> caffeine_lang@common@refinement_types:refinement_types(caffeine_lang@common@accepted_types:accepted_types()).
resolve_refinement_aliases(Refinement, Aliases) ->
case Refinement of
{one_of, Inner, Values} ->
{one_of, resolve_type_aliases(Inner, Aliases), Values};
{inclusive_range, Inner@1, Min, Max} ->
{inclusive_range, resolve_type_aliases(Inner@1, Aliases), Min, Max}
end.