Current section

Files

Jump to
caffeine_lang src caffeine_lang@linker@ir_builder.erl
Raw

src/caffeine_lang@linker@ir_builder.erl

-module(caffeine_lang@linker@ir_builder).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/caffeine_lang/linker/ir_builder.gleam").
-export([reserved_labels/1, build_all/4]).
-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/linker/ir_builder.gleam", 29).
?DOC(false).
-spec reserved_labels(
gleam@dict:dict(binary(), caffeine_lang@linker@slo_params:param_info())
) -> gleam@set:set(binary()).
reserved_labels(Params) ->
_pipe = Params,
_pipe@1 = maps:keys(_pipe),
gleam@set:from_list(_pipe@1).
-file("src/caffeine_lang/linker/ir_builder.gleam", 333).
?DOC(
" Build the typed indicator dict (`Dict(String, IndicatorSource)`) from the\n"
" raw indicators value-tuple. String-valued indicators become `LiteralQuery`\n"
" (the legacy inline-query form). `ExternalIndicatorValue` entries become\n"
" `ExternalSignal`, with `value_extraction` populated by looking up the\n"
" indicator name in `external_indicator_types` — that's how the AST-level\n"
" type info from the `value:` clause survives the trip through lowering.\n"
" Indicators whose values are neither shape (shouldn't happen post-validate)\n"
" are silently dropped; the validator should have caught them earlier.\n"
).
-spec build_indicator_sources(
gleam@dict:dict(binary(), caffeine_lang@helpers:value_tuple()),
gleam@dict:dict(binary(), caffeine_lang@types:accepted_types())
) -> gleam@dict:dict(binary(), caffeine_lang@linker@ir:indicator_source()).
build_indicator_sources(Index, External_indicator_types) ->
case gleam_stdlib:map_get(Index, <<"indicators"/utf8>>) of
{error, _} ->
maps:new();
{ok, Vt} ->
case erlang:element(4, Vt) of
{dict_value, D} ->
_pipe = D,
_pipe@1 = maps:to_list(_pipe),
_pipe@2 = gleam@list:filter_map(
_pipe@1,
fun(Pair) ->
{Name, Val} = Pair,
case Val of
{string_value, S} ->
{ok, {Name, {literal_query, S}}};
{external_indicator_value,
Source,
Match,
Value_path} ->
Value_extraction = case Value_path of
none ->
none;
{some, Path} ->
case gleam_stdlib:map_get(
External_indicator_types,
Name
) of
{ok, T} ->
{some,
{external_value_extraction,
Path,
T}};
{error, _} ->
none
end
end,
{ok,
{Name,
{external_signal,
Source,
Match,
Value_extraction}}};
_ ->
{error, nil}
end
end
),
maps:from_list(_pipe@2);
_ ->
maps:new()
end
end.
-file("src/caffeine_lang/linker/ir_builder.gleam", 387).
?DOC(
" Extract SLO-specific fields from an indexed Dict of ValueTuples.\n"
" Threshold defaults to the standard default when not present (e.g. unmeasured expectations).\n"
" `description` is sourced from the expectation's leading doc comments, not\n"
" from value tuples. `expectation_type` flows from the bound measurement's\n"
" declared `success_rate` / `time_slice` header (None for unmeasured).\n"
" `external_indicator_types` is the measurement's resolved type constraints\n"
" for external-indicator value extractions, keyed by indicator name; empty\n"
" for unmeasured expectations.\n"
).
-spec build_slo_fields(
gleam@dict:dict(binary(), caffeine_lang@helpers:value_tuple()),
gleam@option:option(binary()),
gleam@option:option(caffeine_lang@frontend@ast:expectation_type()),
gleam@dict:dict(binary(), caffeine_lang@types:accepted_types())
) -> caffeine_lang@linker@ir:slo_fields().
build_slo_fields(Index, Description, Expectation_type, External_indicator_types) ->
Threshold = begin
_pipe = caffeine_lang@helpers:extract_value(
Index,
<<"threshold"/utf8>>,
fun caffeine_lang@value:extract_percentage/1
),
gleam@result:unwrap(_pipe, 99.9)
end,
Indicators = build_indicator_sources(Index, External_indicator_types),
Window_in_days = caffeine_lang@helpers:extract_window_in_days(Index),
Evaluation = begin
_pipe@1 = caffeine_lang@helpers:extract_value(
Index,
<<"evaluation"/utf8>>,
fun caffeine_lang@value:extract_string/1
),
gleam@option:from_result(_pipe@1)
end,
Tags = caffeine_lang@helpers:extract_tags(Index),
Runbook = begin
_pipe@2 = caffeine_lang@helpers:extract_value(
Index,
<<"runbook"/utf8>>,
fun(V) -> case V of
nil_value ->
{ok, none};
{string_value, S} ->
{ok, {some, S}};
_ ->
{error, nil}
end end
),
gleam@result:unwrap(_pipe@2, none)
end,
Relations = caffeine_lang@helpers:extract_depends_on(Index),
Depends_on = case gleam@dict:is_empty(Relations) of
true ->
none;
false ->
{some, Relations}
end,
Below_ms = begin
_pipe@3 = caffeine_lang@helpers:extract_value(
Index,
<<"below_ms"/utf8>>,
fun caffeine_lang@value:extract_float/1
),
gleam@option:from_result(_pipe@3)
end,
{slo_fields,
Threshold,
Indicators,
Window_in_days,
Evaluation,
Tags,
Runbook,
Depends_on,
Description,
Below_ms,
Expectation_type}.
-file("src/caffeine_lang/linker/ir_builder.gleam", 279).
?DOC(
" Resolves a value tuple to a list of strings for use as tags.\n"
" Primitives and refinements produce a single-element list.\n"
" Lists are exploded into multiple string values.\n"
" Dicts and type alias refs are unsupported.\n"
" Optional(None) produces an empty list (filtered out).\n"
" Defaulted(None) produces the default value.\n"
).
-spec resolve_values_for_tag(
caffeine_lang@types:accepted_types(),
caffeine_lang@value:value()
) -> {ok, list(binary())} | {error, nil}.
resolve_values_for_tag(Typ, Val) ->
case Typ of
{primitive_type, _} ->
{ok, [caffeine_lang@value:to_string(Val)]};
{refinement_type, Refinement} ->
case Refinement of
{one_of, Inner, _} ->
resolve_values_for_tag(Inner, Val);
{inclusive_range, Inner@1, _, _} ->
resolve_values_for_tag(Inner@1, Val)
end;
{collection_type, {list, Inner@2}} ->
case Val of
{list_value, Items} ->
_pipe = Items,
_pipe@2 = gleam@list:try_map(
_pipe,
fun(Item) ->
_pipe@1 = resolve_values_for_tag(Inner@2, Item),
gleam@result:map(
_pipe@1,
fun(Strings) -> Strings end
)
end
),
gleam@result:map(_pipe@2, fun lists:append/1);
_ ->
{error, nil}
end;
{collection_type, {dict, _, _}} ->
{error, nil};
{record_type, _} ->
{error, nil};
{modifier_type, {optional, Inner@3}} ->
case Val of
nil_value ->
{ok, []};
_ ->
resolve_values_for_tag(Inner@3, Val)
end;
{modifier_type, {defaulted, Inner@4, Default}} ->
case Val of
nil_value ->
case Inner@4 of
{collection_type, {list, _}} ->
{ok,
caffeine_lang@types:parse_list_default_string(
Default
)};
_ ->
{ok, [Default]}
end;
_ ->
resolve_values_for_tag(Inner@4, Val)
end
end.
-file("src/caffeine_lang/linker/ir_builder.gleam", 254).
?DOC(
" Extract misc metadata from value tuples.\n"
" Filters out reserved labels (derived from artifact params) and unsupported types.\n"
" Each key maps to a list of string values (primitives become single-element\n"
" lists, collection lists are exploded, nulls are excluded).\n"
).
-spec extract_misc_metadata(
list(caffeine_lang@helpers:value_tuple()),
gleam@set:set(binary())
) -> gleam@dict:dict(binary(), list(binary())).
extract_misc_metadata(Value_tuples, Reserved_labels) ->
_pipe = Value_tuples,
_pipe@1 = gleam@list:filter_map(
_pipe,
fun(Value_tuple) ->
gleam@bool:guard(
gleam@set:contains(
Reserved_labels,
erlang:element(2, Value_tuple)
),
{error, nil},
fun() ->
case resolve_values_for_tag(
erlang:element(3, Value_tuple),
erlang:element(4, Value_tuple)
) of
{ok, []} ->
{error, nil};
{ok, Values} ->
{ok, {erlang:element(2, Value_tuple), Values}};
{error, _} ->
{error, nil}
end
end
)
end
),
maps:from_list(_pipe@1).
-file("src/caffeine_lang/linker/ir_builder.gleam", 231).
?DOC(
" Build value tuples for Optional/Defaulted params that weren't provided.\n"
" These need to be in value_tuples so the templatizer can resolve them.\n"
).
-spec build_unprovided_optional_value_tuples(
gleam@dict:dict(binary(), caffeine_lang@value:value()),
gleam@dict:dict(binary(), caffeine_lang@types:accepted_types())
) -> list(caffeine_lang@helpers:value_tuple()).
build_unprovided_optional_value_tuples(Merged_inputs, Params) ->
_pipe = Params,
_pipe@1 = maps:to_list(_pipe),
gleam@list:filter_map(
_pipe@1,
fun(Param) ->
{Label, Typ} = Param,
gleam@bool:guard(
gleam@dict:has_key(Merged_inputs, Label),
{error, nil},
fun() ->
case caffeine_lang@types:is_optional_or_defaulted(Typ) of
true ->
{ok, {value_tuple, Label, Typ, nil_value}};
false ->
{error, nil}
end
end
)
end
).
-file("src/caffeine_lang/linker/ir_builder.gleam", 214).
?DOC(" Build value tuples from provided inputs.\n").
-spec build_provided_value_tuples(
gleam@dict:dict(binary(), caffeine_lang@value:value()),
gleam@dict:dict(binary(), caffeine_lang@types:accepted_types())
) -> list(caffeine_lang@helpers:value_tuple()).
build_provided_value_tuples(Merged_inputs, Params) ->
_pipe = Merged_inputs,
_pipe@1 = maps:to_list(_pipe),
gleam@list:filter_map(
_pipe@1,
fun(Pair) ->
{Label, Val} = Pair,
case gleam_stdlib:map_get(Params, Label) of
{ok, Typ} ->
{ok, {value_tuple, Label, Typ, Val}};
{error, nil} ->
{error, nil}
end
end
).
-file("src/caffeine_lang/linker/ir_builder.gleam", 204).
?DOC(
" Build value tuples from merged inputs and params.\n"
" Includes both provided inputs and unprovided Optional/Defaulted params.\n"
).
-spec build_value_tuples(
gleam@dict:dict(binary(), caffeine_lang@value:value()),
gleam@dict:dict(binary(), caffeine_lang@types:accepted_types())
) -> list(caffeine_lang@helpers:value_tuple()).
build_value_tuples(Merged_inputs, Params) ->
Provided = build_provided_value_tuples(Merged_inputs, Params),
Unprovided = build_unprovided_optional_value_tuples(Merged_inputs, Params),
lists:append(Provided, Unprovided).
-file("src/caffeine_lang/linker/ir_builder.gleam", 169).
?DOC(
" Build an IR from an unmeasured expectation.\n"
" Uses restricted params (threshold, window_in_days, depends_on) and sets vendor to None.\n"
).
-spec build_unmeasured(
caffeine_lang@linker@expectations:expectation(),
binary(),
binary(),
binary(),
gleam@set:set(binary()),
gleam@dict:dict(binary(), caffeine_lang@types:accepted_types())
) -> caffeine_lang@linker@ir:intermediate_representation(caffeine_lang@linker@ir:linked()).
build_unmeasured(
Expectation,
Org,
Team,
Service,
Reserved_labels,
Unmeasured_params
) ->
Value_tuples = build_value_tuples(
erlang:element(4, Expectation),
Unmeasured_params
),
Index = caffeine_lang@helpers:index_value_tuples(Value_tuples),
Misc_metadata = extract_misc_metadata(Value_tuples, Reserved_labels),
Unique_name = <<<<<<<<Org/binary, "_"/utf8>>/binary, Service/binary>>/binary,
"_"/utf8>>/binary,
(erlang:element(2, Expectation))/binary>>,
Slo = build_slo_fields(
Index,
erlang:element(5, Expectation),
none,
maps:new()
),
{intermediate_representation,
{intermediate_representation_meta_data,
{expectation_label, erlang:element(2, Expectation)},
{org_name, Org},
{service_name, Service},
{measurement_name, <<"unmeasured"/utf8>>},
{team_name, Team},
Misc_metadata},
Unique_name,
Value_tuples,
Slo,
none}.
-file("src/caffeine_lang/linker/ir_builder.gleam", 106).
?DOC(" Build an IR from a measured expectation paired with its measurement.\n").
-spec build_measured(
caffeine_lang@linker@expectations:expectation(),
caffeine_lang@linker@measurements:measurement(caffeine_lang@linker@measurements:measurement_validated()),
binary(),
binary(),
binary(),
gleam@set:set(binary()),
gleam@dict:dict(binary(), caffeine_lang@analysis@vendor:vendor())
) -> {ok,
caffeine_lang@linker@ir:intermediate_representation(caffeine_lang@linker@ir:linked())} |
{error, caffeine_lang@errors:compilation_error()}.
build_measured(
Expectation,
Measurement,
Org,
Team,
Service,
Reserved_labels,
Vendor_lookup
) ->
Merged_inputs = maps:merge(
erlang:element(4, Measurement),
erlang:element(4, Expectation)
),
Value_tuples = build_value_tuples(
Merged_inputs,
erlang:element(3, Measurement)
),
Index = caffeine_lang@helpers:index_value_tuples(Value_tuples),
Misc_metadata = extract_misc_metadata(Value_tuples, Reserved_labels),
Unique_name = <<<<<<<<Org/binary, "_"/utf8>>/binary, Service/binary>>/binary,
"_"/utf8>>/binary,
(erlang:element(2, Expectation))/binary>>,
Slo = build_slo_fields(
Index,
erlang:element(5, Expectation),
erlang:element(6, Measurement),
erlang:element(5, Measurement)
),
Resolved_vendor = case gleam_stdlib:map_get(
Vendor_lookup,
erlang:element(2, Measurement)
) of
{ok, V} ->
{ok, {some, V}};
{error, nil} ->
{error,
caffeine_lang@errors:linker_vendor_resolution_error(
<<<<<<<<<<<<<<<<<<<<"expectation '"/utf8, Org/binary>>/binary,
"."/utf8>>/binary,
Team/binary>>/binary,
"."/utf8>>/binary,
Service/binary>>/binary,
"."/utf8>>/binary,
(erlang:element(2, Expectation))/binary>>/binary,
"' - measurement '"/utf8>>/binary,
(erlang:element(2, Measurement))/binary>>/binary,
"' has no associated vendor"/utf8>>
)}
end,
gleam@result:'try'(
Resolved_vendor,
fun(Resolved_vendor@1) ->
{ok,
{intermediate_representation,
{intermediate_representation_meta_data,
{expectation_label, erlang:element(2, Expectation)},
{org_name, Org},
{service_name, Service},
{measurement_name, erlang:element(2, Measurement)},
{team_name, Team},
Misc_metadata},
Unique_name,
Value_tuples,
Slo,
Resolved_vendor@1}}
end
).
-file("src/caffeine_lang/linker/ir_builder.gleam", 66).
?DOC(" Build intermediate representations from validated expectations for a single file.\n").
-spec build(
list({caffeine_lang@linker@expectations:expectation(),
gleam@option:option(caffeine_lang@linker@measurements:measurement(caffeine_lang@linker@measurements:measurement_validated()))}),
binary(),
gleam@set:set(binary()),
gleam@dict:dict(binary(), caffeine_lang@analysis@vendor:vendor()),
gleam@dict:dict(binary(), caffeine_lang@types:accepted_types())
) -> {ok,
list(caffeine_lang@linker@ir:intermediate_representation(caffeine_lang@linker@ir:linked()))} |
{error, caffeine_lang@errors:compilation_error()}.
build(
Expectations_measurement_collection,
File_path,
Reserved_labels,
Vendor_lookup,
Unmeasured_params
) ->
{Org, Team, Service} = caffeine_lang@helpers:extract_path_prefix(File_path),
_pipe = Expectations_measurement_collection,
gleam@list:try_map(
_pipe,
fun(Expectation_and_measurement_pair) ->
{Expectation, Maybe_measurement} = Expectation_and_measurement_pair,
case Maybe_measurement of
{some, Measurement} ->
build_measured(
Expectation,
Measurement,
Org,
Team,
Service,
Reserved_labels,
Vendor_lookup
);
none ->
{ok,
build_unmeasured(
Expectation,
Org,
Team,
Service,
Reserved_labels,
Unmeasured_params
)}
end
end
).
-file("src/caffeine_lang/linker/ir_builder.gleam", 37).
?DOC(false).
-spec build_all(
list({list({caffeine_lang@linker@expectations:expectation(),
gleam@option:option(caffeine_lang@linker@measurements:measurement(caffeine_lang@linker@measurements:measurement_validated()))}),
binary()}),
gleam@set:set(binary()),
gleam@dict:dict(binary(), caffeine_lang@analysis@vendor:vendor()),
gleam@dict:dict(binary(), caffeine_lang@linker@slo_params:param_info())
) -> {ok,
list(caffeine_lang@linker@ir:intermediate_representation(caffeine_lang@linker@ir:linked()))} |
{error, caffeine_lang@errors:compilation_error()}.
build_all(Expectations_with_paths, Reserved_labels, Vendor_lookup, Params) ->
Unmeasured_params = caffeine_lang@linker@slo_params:unmeasured_param_types(
Params
),
_pipe = Expectations_with_paths,
_pipe@1 = gleam@list:map(
_pipe,
fun(Pair) ->
{Expectations_measurement_collection, File_path} = Pair,
build(
Expectations_measurement_collection,
File_path,
Reserved_labels,
Vendor_lookup,
Unmeasured_params
)
end
),
_pipe@2 = caffeine_lang@errors:from_results(_pipe@1),
gleam@result:map(_pipe@2, fun lists:append/1).