-module(caffeine_query_language@generator). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "src/caffeine_query_language/generator.gleam"). -export([substitute_words/2, extract_words/1, resolve_slo_query_typed/2, resolve_slo_to_expression/2, resolve_slo_to_hcl/2]). -export_type([named_query/0, resolved_slo_query/0, slo_type/0, resolved_slo_hcl/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 named_query() :: {named_query, binary(), binary()}. -type resolved_slo_query() :: {resolved_good_over_total, binary(), binary()} | {resolved_time_slice, binary(), integer(), float(), binary(), list(named_query())}. -type slo_type() :: metric_slo | time_slice_slo. -type resolved_slo_hcl() :: {resolved_slo_hcl, slo_type(), list(terra_madre@hcl:block())}. -file("src/caffeine_query_language/generator.gleam", 50). ?DOC(false). -spec substitute_words( caffeine_query_language@ast:exp(any()), gleam@dict:dict(binary(), binary()) ) -> caffeine_query_language@ast:exp(caffeine_query_language@ast:substituted()). substitute_words(Exp, Substitutions) -> case Exp of {primary, {primary_word, {word, Name}}} -> Value = begin _pipe = gleam_stdlib:map_get(Substitutions, Name), gleam@result:unwrap(_pipe, Name) end, {primary, {primary_word, {word, Value}}}; {primary, {primary_exp, Inner}} -> {primary, {primary_exp, substitute_words(Inner, Substitutions)}}; {time_slice_expr, Spec} -> Query = begin _pipe@1 = gleam_stdlib:map_get( Substitutions, erlang:element(2, Spec) ), gleam@result:unwrap(_pipe@1, erlang:element(2, Spec)) end, {time_slice_expr, {time_slice_exp, Query, erlang:element(3, Spec), erlang:element(4, Spec), erlang:element(5, Spec)}}; {operator_expr, Left, Right, Op} -> {operator_expr, substitute_words(Left, Substitutions), substitute_words(Right, Substitutions), Op} end. -file("src/caffeine_query_language/generator.gleam", 85). ?DOC(" Accumulates unique word names into a Set.\n"). -spec extract_words_loop( caffeine_query_language@ast:exp(any()), gleam@set:set(binary()) ) -> gleam@set:set(binary()). extract_words_loop(Exp, Acc) -> case Exp of {primary, {primary_word, {word, Name}}} -> gleam@set:insert(Acc, Name); {primary, {primary_exp, Inner}} -> extract_words_loop(Inner, Acc); {time_slice_expr, _} -> Acc; {operator_expr, Left, Right, _} -> extract_words_loop(Right, extract_words_loop(Left, Acc)) end. -file("src/caffeine_query_language/generator.gleam", 78). ?DOC(false). -spec extract_words(caffeine_query_language@ast:exp(any())) -> list(binary()). extract_words(Exp) -> _pipe = extract_words_loop(Exp, gleam@set:new()), _pipe@1 = gleam@set:to_list(_pipe), gleam@list:sort(_pipe@1, fun gleam@string:compare/2). -file("src/caffeine_query_language/generator.gleam", 98). ?DOC(false). -spec resolve_slo_query_typed(binary(), gleam@dict:dict(binary(), binary())) -> {ok, resolved_slo_query()} | {error, binary()}. resolve_slo_query_typed(Value_expr, Substitutions) -> case caffeine_query_language@parser:parse_expr(Value_expr) of {error, Err} -> {error, <<"Parse error: "/utf8, Err/binary>>}; {ok, Exp} -> case caffeine_query_language@resolver:resolve_primitives(Exp) of {ok, {good_over_total, Numerator_exp, Denominator_exp}} -> Numerator_str = begin _pipe = substitute_words(Numerator_exp, Substitutions), caffeine_query_language@printer:exp_to_string(_pipe) end, Denominator_str = begin _pipe@1 = substitute_words( Denominator_exp, Substitutions ), caffeine_query_language@printer:exp_to_string(_pipe@1) end, {ok, {resolved_good_over_total, Numerator_str, Denominator_str}}; {ok, {time_slice, Comparator, Interval_seconds, Threshold, Query}} -> Comparator_str = case Comparator of less_than -> <<"<"/utf8>>; less_than_or_equal_to -> <<"<="/utf8>>; greater_than -> <<">"/utf8>>; greater_than_or_equal_to -> <<">="/utf8>> end, case caffeine_query_language@parser:parse_expr(Query) of {ok, Query_exp} -> Words = extract_words(Query_exp), Named_queries = begin _pipe@2 = Words, gleam@list:filter_map( _pipe@2, fun(Word) -> case gleam_stdlib:map_get( Substitutions, Word ) of {ok, Resolved} -> {ok, {named_query, Word, Resolved}}; {error, _} -> {error, nil} end end ) end, case Named_queries of [] -> {ok, {resolved_time_slice, Comparator_str, Interval_seconds, Threshold, <<"query1"/utf8>>, [{named_query, <<"query1"/utf8>>, Query}]}}; _ -> Formula_expr = caffeine_query_language@printer:strip_outer_parens( Query ), {ok, {resolved_time_slice, Comparator_str, Interval_seconds, Threshold, Formula_expr, Named_queries}} end; {error, _} -> Resolved_query = begin _pipe@3 = gleam_stdlib:map_get( Substitutions, Query ), gleam@result:unwrap(_pipe@3, Query) end, {ok, {resolved_time_slice, Comparator_str, Interval_seconds, Threshold, <<"query1"/utf8>>, [{named_query, <<"query1"/utf8>>, Resolved_query}]}} end; {error, Err@1} -> {error, <<"Resolution error: "/utf8, (caffeine_lang@errors:to_message(Err@1))/binary>>} end end. -file("src/caffeine_query_language/generator.gleam", 205). ?DOC( " Validate that all words in an expression exist in the substitutions dict.\n" " Returns an error listing any missing indicator names.\n" ). -spec validate_words_exist( caffeine_query_language@ast:exp(any()), gleam@dict:dict(binary(), binary()), fun(() -> {ok, binary()} | {error, binary()}) ) -> {ok, binary()} | {error, binary()}. validate_words_exist(Exp, Substitutions, Next) -> Missing = begin _pipe = extract_words(Exp), gleam@list:filter( _pipe, fun(Word) -> case gleam_stdlib:map_get(Substitutions, Word) of {ok, _} -> false; {error, _} -> true end end ) end, case Missing of [] -> Next(); _ -> {error, <<"evaluation references undefined indicators: "/utf8, (gleam@string:join(Missing, <<", "/utf8>>))/binary>>} end. -file("src/caffeine_query_language/generator.gleam", 181). ?DOC(false). -spec resolve_slo_to_expression(binary(), gleam@dict:dict(binary(), binary())) -> {ok, binary()} | {error, binary()}. resolve_slo_to_expression(Value_expr, Substitutions) -> gleam@result:'try'( begin _pipe = caffeine_query_language@parser:parse_expr(Value_expr), gleam@result:map_error( _pipe, fun(Err) -> <<"Parse error: "/utf8, Err/binary>> end ) end, fun(Parsed) -> Exp = case caffeine_query_language@resolver:resolve_primitives( Parsed ) of {ok, {good_over_total, Num, Den}} -> {ok, {operator_expr, Num, Den, 'div'}}; {ok, {time_slice, _, _, _, _}} -> {error, <<"time_slice expressions are not supported for expression resolution"/utf8>>}; {error, _} -> {ok, Parsed} end, gleam@result:'try'( Exp, fun(Exp@1) -> validate_words_exist( Exp@1, Substitutions, fun() -> {ok, begin _pipe@1 = substitute_words( Exp@1, Substitutions ), caffeine_query_language@printer:exp_to_string( _pipe@1 ) end} end ) end ) end ). -file("src/caffeine_query_language/generator.gleam", 231). ?DOC(false). -spec resolve_slo_to_hcl(binary(), gleam@dict:dict(binary(), binary())) -> {ok, resolved_slo_hcl()} | {error, binary()}. resolve_slo_to_hcl(Value_expr, Substitutions) -> case resolve_slo_query_typed(Value_expr, Substitutions) of {ok, {resolved_good_over_total, Numerator, Denominator}} -> Query_block = terra_madre@hcl:simple_block( <<"query"/utf8>>, [{<<"numerator"/utf8>>, {string_literal, Numerator}}, {<<"denominator"/utf8>>, {string_literal, Denominator}}] ), {ok, {resolved_slo_hcl, metric_slo, [Query_block]}}; {ok, {resolved_time_slice, Comparator, Interval_seconds, Threshold, Formula_expression, Named_queries}} -> Inner_query_blocks = begin _pipe = Named_queries, gleam@list:map( _pipe, fun(Nq) -> Metric_query_block = {block, <<"metric_query"/utf8>>, [], maps:from_list( [{<<"data_source"/utf8>>, {string_literal, <<"metrics"/utf8>>}}, {<<"name"/utf8>>, {string_literal, erlang:element(2, Nq)}}, {<<"query"/utf8>>, {string_literal, erlang:element(3, Nq)}}] ), []}, {block, <<"query"/utf8>>, [], maps:new(), [Metric_query_block]} end ) end, Formula_block = {block, <<"formula"/utf8>>, [], maps:from_list( [{<<"formula_expression"/utf8>>, {string_literal, Formula_expression}}] ), []}, Outer_query_block = {block, <<"query"/utf8>>, [], maps:new(), [Formula_block | Inner_query_blocks]}, Time_slice_block = {block, <<"time_slice"/utf8>>, [], maps:from_list( [{<<"comparator"/utf8>>, {string_literal, Comparator}}, {<<"query_interval_seconds"/utf8>>, {int_literal, Interval_seconds}}, {<<"threshold"/utf8>>, {float_literal, Threshold}}] ), [Outer_query_block]}, Sli_specification_block = {block, <<"sli_specification"/utf8>>, [], maps:new(), [Time_slice_block]}, {ok, {resolved_slo_hcl, time_slice_slo, [Sli_specification_block]}}; {error, Err} -> {error, Err} end.