Current section

Files

Jump to
caffeine_lang src caffeine_query_language@generator.erl
Raw

src/caffeine_query_language@generator.erl

-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", 48).
-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", 57).
-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", 83).
-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", 93).
-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", 69).
-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", 149).
?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", 162).
?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", 191).
?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", 223).
?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", 204).
?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", 103).
-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", 15).
?DOC(" Converts an expression AST node to its string representation.\n").
-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),
<<<<<<<<Left/binary, " "/utf8>>/binary, Op/binary>>/binary,
" "/utf8>>/binary,
Right/binary>>
end
end.
-file("src/caffeine_query_language/generator.gleam", 138).
-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", 112).
-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),
<<<<<<<<Left/binary, " "/utf8>>/binary, Op/binary>>/binary,
" "/utf8>>/binary,
Right/binary>>
end
end.
-file("src/caffeine_query_language/generator.gleam", 269).
?DOC(
" Parse a value expression, resolve to primitive, substitute words,\n"
" and return the resolved SLO query type.\n"
).
-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", 353).
?DOC(
" Parse a value expression, resolve to GoodOverTotal primitive, substitute words,\n"
" and return the numerator and denominator as strings.\n"
" Panics if parsing or resolution fails.\n"
).
-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", 365).
?DOC(
" Parse a value expression, resolve to primitive, substitute words,\n"
" and return HCL blocks ready for Datadog terraform generation.\n"
).
-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.