-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([operator_to_datadog_query/1, substitute_words/2, extract_words/1, exp_to_string/1, resolve_slo_query_typed/2, resolve_slo_query/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). -spec comparator_to_string(caffeine_query_language@parser:comparator()) -> binary(). comparator_to_string(Comparator) -> case Comparator of less_than -> <<"<"/utf8>>; less_than_or_equal_to -> <<"<="/utf8>>; greater_than -> <<">"/utf8>>; greater_than_or_equal_to -> <<">="/utf8>> end. -file("src/caffeine_query_language/generator.gleam", 59). -spec float_to_string(float()) -> binary(). float_to_string(F) -> Truncated = erlang:trunc(F), Is_whole = erlang:float(Truncated) =:= F, case Is_whole of true -> erlang:integer_to_binary(Truncated); false -> gleam_stdlib:float_to_string(F) end. -file("src/caffeine_query_language/generator.gleam", 85). -spec get_leftmost_word(caffeine_query_language@parser:exp()) -> gleam@option:option(binary()). get_leftmost_word(Exp) -> case Exp of {primary, {primary_word, {word, W}}} -> {some, W}; {primary, {primary_exp, Inner_exp}} -> get_leftmost_word(Inner_exp); {time_slice_expr, _} -> none; {operator_expr, Left, _, _} -> get_leftmost_word(Left) end. -file("src/caffeine_query_language/generator.gleam", 95). -spec all_divisions(caffeine_query_language@parser:exp()) -> boolean(). all_divisions(Exp) -> case Exp of {primary, _} -> true; {operator_expr, Left, Right, 'div'} -> all_divisions(Left) andalso all_divisions(Right); _ -> false end. -file("src/caffeine_query_language/generator.gleam", 71). -spec is_path_expression(caffeine_query_language@parser:exp()) -> boolean(). is_path_expression(Exp) -> case get_leftmost_word(Exp) of {some, W} -> case gleam_stdlib:string_ends_with(W, <<":"/utf8>>) of true -> all_divisions(Exp); false -> false end; none -> false end. -file("src/caffeine_query_language/generator.gleam", 153). ?DOC(false). -spec operator_to_datadog_query(caffeine_query_language@parser:operator()) -> binary(). operator_to_datadog_query(Operator) -> case Operator of add -> <<"+"/utf8>>; sub -> <<"-"/utf8>>; mul -> <<"*"/utf8>>; 'div' -> <<"/"/utf8>> end. -file("src/caffeine_query_language/generator.gleam", 166). ?DOC(false). -spec substitute_words( caffeine_query_language@parser:exp(), gleam@dict:dict(binary(), binary()) ) -> caffeine_query_language@parser:exp(). 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", 195). ?DOC(false). -spec extract_words(caffeine_query_language@parser:exp()) -> list(binary()). extract_words(Exp) -> case Exp of {primary, {primary_word, {word, Name}}} -> [Name]; {primary, {primary_exp, Inner}} -> extract_words(Inner); {time_slice_expr, _} -> []; {operator_expr, Left, Right, _} -> _pipe = lists:append(extract_words(Left), extract_words(Right)), gleam@list:unique(_pipe) end. -file("src/caffeine_query_language/generator.gleam", 225). ?DOC(" Check if parentheses are balanced in a string.\n"). -spec is_balanced(binary(), integer()) -> boolean(). is_balanced(S, Depth) -> case gleam_stdlib:string_pop_grapheme(S) of {error, _} -> Depth =:= 0; {ok, {<<"("/utf8>>, Rest}} -> is_balanced(Rest, Depth + 1); {ok, {<<")"/utf8>>, Rest@1}} -> case Depth of 0 -> false; _ -> is_balanced(Rest@1, Depth - 1) end; {ok, {_, Rest@2}} -> is_balanced(Rest@2, Depth) end. -file("src/caffeine_query_language/generator.gleam", 208). ?DOC( " Strips outer parentheses from a string if they wrap the entire expression.\n" " E.g., \"(a + b)\" -> \"a + b\", but \"(a + b) * c\" stays unchanged.\n" ). -spec strip_outer_parens(binary()) -> binary(). strip_outer_parens(S) -> Trimmed = gleam@string:trim(S), case gleam_stdlib:string_starts_with(Trimmed, <<"("/utf8>>) andalso gleam_stdlib:string_ends_with( Trimmed, <<")"/utf8>> ) of true -> Inner = gleam@string:slice(Trimmed, 1, string:length(Trimmed) - 2), case is_balanced(Inner, 0) of true -> Inner; false -> Trimmed end; false -> Trimmed end. -file("src/caffeine_query_language/generator.gleam", 105). -spec exp_to_string_no_spaces(caffeine_query_language@parser:exp()) -> binary(). exp_to_string_no_spaces(Exp) -> case Exp of {primary, {primary_word, {word, W}}} -> W; {operator_expr, Left, Right, 'div'} -> <<<<(exp_to_string_no_spaces(Left))/binary, "/"/utf8>>/binary, (exp_to_string_no_spaces(Right))/binary>>; _ -> exp_to_string(Exp) end. -file("src/caffeine_query_language/generator.gleam", 16). ?DOC(false). -spec exp_to_string(caffeine_query_language@parser:exp()) -> binary(). exp_to_string(Exp) -> case Exp of {primary, Primary} -> primary_to_string(Primary, none); {time_slice_expr, Spec} -> <<<<<<<<<<<<<<<<"time_slice("/utf8, (erlang:element(2, Spec))/binary>>/binary, " "/utf8>>/binary, (comparator_to_string( erlang:element(3, Spec) ))/binary>>/binary, " "/utf8>>/binary, (float_to_string(erlang:element(4, Spec)))/binary>>/binary, " per "/utf8>>/binary, (float_to_string(erlang:element(5, Spec)))/binary>>/binary, "s)"/utf8>>; {operator_expr, Numerator, Denominator, Operator} -> case {Operator, is_path_expression(Exp)} of {'div', true} -> exp_to_string_no_spaces(Exp); {_, _} -> Left = exp_to_string_with_context( Numerator, {some, Operator}, true ), Right = exp_to_string_with_context( Denominator, {some, Operator}, false ), Op = operator_to_datadog_query(Operator), <<<<<<<>/binary, Op/binary>>/binary, " "/utf8>>/binary, Right/binary>> end end. -file("src/caffeine_query_language/generator.gleam", 141). -spec primary_to_string( caffeine_query_language@parser:primary(), gleam@option:option(caffeine_query_language@parser:operator()) ) -> binary(). primary_to_string(Primary, _) -> case Primary of {primary_word, Word} -> erlang:element(2, Word); {primary_exp, Exp} -> <<<<"("/utf8, (exp_to_string(Exp))/binary>>/binary, ")"/utf8>> end. -file("src/caffeine_query_language/generator.gleam", 114). -spec exp_to_string_with_context( caffeine_query_language@parser:exp(), gleam@option:option(caffeine_query_language@parser:operator()), boolean() ) -> binary(). exp_to_string_with_context(Exp, Parent_op, _) -> case Exp of {primary, Primary} -> primary_to_string(Primary, Parent_op); {time_slice_expr, _} -> exp_to_string(Exp); {operator_expr, Numerator, Denominator, Operator} -> case {Operator, is_path_expression(Exp)} of {'div', true} -> exp_to_string_no_spaces(Exp); {_, _} -> Left = exp_to_string_with_context( Numerator, {some, Operator}, true ), Right = exp_to_string_with_context( Denominator, {some, Operator}, false ), Op = operator_to_datadog_query(Operator), <<<<<<<>/binary, Op/binary>>/binary, " "/utf8>>/binary, Right/binary>> end end. -file("src/caffeine_query_language/generator.gleam", 272). ?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_container} -> case caffeine_query_language@resolver:resolve_primitives( Exp_container ) of {ok, {good_over_total, Numerator_exp, Denominator_exp}} -> Numerator_str = begin _pipe = substitute_words(Numerator_exp, Substitutions), exp_to_string(_pipe) end, Denominator_str = begin _pipe@1 = substitute_words( Denominator_exp, Substitutions ), 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_container} -> Query_exp = erlang:element(2, Query_exp_container), 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 = 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, (erlang:element(2, Err@1))/binary>>} end end. -file("src/caffeine_query_language/generator.gleam", 361). ?DOC(false). -spec resolve_slo_query(binary(), gleam@dict:dict(binary(), binary())) -> {binary(), binary()}. resolve_slo_query(Value_expr, Substitutions) -> case resolve_slo_query_typed(Value_expr, Substitutions) of {ok, {resolved_good_over_total, Numerator, Denominator}} -> {Numerator, Denominator}; _ -> {<<""/utf8>>, <<""/utf8>>} end. -file("src/caffeine_query_language/generator.gleam", 377). ?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.