Packages
caffeine_lang
5.4.3
6.3.1
6.3.0
6.2.2
6.2.1
6.2.0
6.1.2
6.1.1
6.1.0
6.0.0
5.6.0
5.5.0
5.4.4
5.4.3
5.4.2
5.4.1
5.4.0
5.3.0
5.2.0
5.1.1
5.1.0
5.0.12
5.0.11
5.0.10
5.0.8
5.0.7
5.0.6
5.0.5
5.0.4
5.0.1
5.0.0
4.10.0
4.9.0
4.8.3
4.8.2
4.8.1
4.8.0
4.7.9
4.7.8
4.7.7
4.7.6
4.7.5
4.6.7
4.6.6
4.6.5
4.6.4
4.6.3
4.6.2
4.6.0
4.5.1
4.5.0
4.4.4
4.4.3
4.4.1
4.4.0
4.3.7
4.3.6
3.0.6
3.0.5
3.0.4
3.0.3
3.0.2
3.0.1
3.0.0
2.0.5
2.0.4
2.0.3
2.0.2
2.0.1
2.0.0
1.0.2
1.0.1
0.1.0
0.0.24
0.0.23
0.0.22
0.0.21
0.0.20
0.0.19
0.0.18
0.0.17
0.0.16
0.0.15
0.0.14
0.0.13
0.0.12
0.0.11
0.0.10
0.0.9
0.0.8
0.0.7
0.0.6
0.0.5
0.0.4
0.0.2
0.0.1
A compiler for generating reliability artifacts from service expectation definitions.
Current section
Files
Jump to
Current section
Files
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_measurements/1, lower_expectations/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", 321).
-spec transform_template_vars_loop(binary(), list(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),
[<<"$$"/utf8>>, Before | Acc]
);
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,
[<<"$$"/utf8>>,
Transformed,
<<"$$"/utf8>>,
Before |
Acc]
);
{error, nil} ->
erlang:list_to_binary(
lists:reverse(
[After, <<"$"/utf8>>, Before | Acc]
)
)
end
end;
{error, nil} ->
erlang:list_to_binary(lists:reverse([Remaining | Acc]))
end.
-file("src/caffeine_lang/frontend/lowering.gleam", 317).
?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, []).
-file("src/caffeine_lang/frontend/lowering.gleam", 292).
?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} ->
{percentage_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", 279).
?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", 267).
?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.
-file("src/caffeine_lang/frontend/lowering.gleam", 255).
?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", 240).
?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", 214).
?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", 195).
?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", 183).
?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", 131).
?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", 147).
?DOC(
" Merges extended fields into a measurement item's requires and provides.\n"
" Order: extended extendables left-to-right, then item's own fields (can override).\n"
).
-spec merge_measurement_extends(
caffeine_lang@frontend@ast:measurement_item(),
gleam@dict:dict(binary(), caffeine_lang@frontend@ast:extendable())
) -> {caffeine_lang@frontend@ast:struct(), caffeine_lang@frontend@ast:struct()}.
merge_measurement_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", 70).
?DOC(" Generates a single measurement from an AST item.\n").
-spec generate_measurement_item(
caffeine_lang@frontend@ast:measurement_item(),
gleam@dict:dict(binary(), caffeine_lang@frontend@ast:extendable()),
gleam@dict:dict(binary(), caffeine_lang@types:parsed_type())
) -> caffeine_lang@linker@measurements:measurement(caffeine_lang@linker@measurements:raw()).
generate_measurement_item(Item, Extendables, Type_aliases) ->
{Merged_requires, Merged_provides} = merge_measurement_extends(
Item,
Extendables
),
Params = struct_to_params(Merged_requires, Type_aliases),
Inputs = struct_to_inputs(Merged_provides),
{measurement, erlang:element(2, Item), Params, Inputs}.
-file("src/caffeine_lang/frontend/lowering.gleam", 53).
?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", 62).
?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", 26).
?DOC(false).
-spec lower_measurements(
caffeine_lang@frontend@ast:measurements_file(caffeine_lang@frontend@ast:validated())
) -> list(caffeine_lang@linker@measurements:measurement(caffeine_lang@linker@measurements:raw())).
lower_measurements(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:map(
_pipe,
fun(Item) ->
generate_measurement_item(Item, Extendables, Type_aliases)
end
).
-file("src/caffeine_lang/frontend/lowering.gleam", 122).
-spec strip_doc_comment_text(binary()) -> binary().
strip_doc_comment_text(Text) ->
Stripped = case gleam_stdlib:string_starts_with(Text, <<" "/utf8>>) of
true ->
gleam@string:drop_start(Text, 1);
false ->
Text
end,
gleam@string:trim_end(Stripped).
-file("src/caffeine_lang/frontend/lowering.gleam", 107).
?DOC(
" Extracts `###` doc-comment text from a node's leading comments and joins\n"
" the lines with `\\n`. Returns `None` when no doc comments are present.\n"
" Each line drops a single leading space (the universal `### ` form) and\n"
" rstrips trailing whitespace. `#` and `##` comments are ignored — they're\n"
" treated as section headers / inline notes, not SLO descriptions.\n"
).
-spec extract_doc_description(list(caffeine_lang@frontend@ast:comment())) -> gleam@option:option(binary()).
extract_doc_description(Comments) ->
Lines = begin
_pipe = Comments,
gleam@list:filter_map(_pipe, fun(C) -> case C of
{doc_comment, Text} ->
{ok, strip_doc_comment_text(Text)};
_ ->
{error, nil}
end end)
end,
case Lines of
[] ->
none;
_ ->
{some, gleam@string:join(Lines, <<"\n"/utf8>>)}
end.
-file("src/caffeine_lang/frontend/lowering.gleam", 169).
?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", 85).
?DOC(" Generates a single expectation from an AST item.\n").
-spec generate_expect_item(
caffeine_lang@frontend@ast:expect_item(),
gleam@option:option(binary()),
gleam@dict:dict(binary(), caffeine_lang@frontend@ast:extendable())
) -> caffeine_lang@linker@expectations:expectation().
generate_expect_item(Item, Measurement, Extendables) ->
Merged_provides = merge_expect_extends(Item, Extendables),
Inputs = struct_to_inputs(Merged_provides),
Description = extract_doc_description(erlang:element(5, Item)),
{expectation, erlang:element(2, Item), Measurement, Inputs, Description}.
-file("src/caffeine_lang/frontend/lowering.gleam", 40).
?DOC(false).
-spec lower_expectations(
caffeine_lang@frontend@ast:expects_file(caffeine_lang@frontend@ast:validated())
) -> 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).