Packages
caffeine_lang
2.0.5
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_query_language@parser.erl
-module(caffeine_query_language@parser).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/caffeine_query_language/parser.gleam").
-export([is_last_char/2, is_balanced_parens/3, find_rightmost_operator_at_level/5, parse_expr/1]).
-export_type(['query'/0, exp_container/0, operator/0, comparator/0, time_slice_exp/0, exp/0, primary/0, word/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 'query'() :: {'query', exp()}.
-type exp_container() :: {exp_container, exp()}.
-type operator() :: add | sub | mul | 'div'.
-type comparator() :: less_than |
less_than_or_equal_to |
greater_than |
greater_than_or_equal_to.
-type time_slice_exp() :: {time_slice_exp,
binary(),
comparator(),
float(),
float()}.
-type exp() :: {time_slice_expr, time_slice_exp()} |
{operator_expr, exp(), exp(), operator()} |
{primary, primary()}.
-type primary() :: {primary_word, word()} | {primary_exp, exp()}.
-type word() :: {word, binary()}.
-file("src/caffeine_query_language/parser.gleam", 233).
-spec find_substring_position_loop(binary(), binary(), integer()) -> gleam@option:option(integer()).
find_substring_position_loop(Haystack, Needle, Pos) ->
Needle_len = string:length(Needle),
Haystack_len = string:length(Haystack),
case (Pos + Needle_len) > Haystack_len of
true ->
none;
false ->
case gleam@string:slice(Haystack, Pos, Needle_len) =:= Needle of
true ->
{some, Pos};
false ->
find_substring_position_loop(Haystack, Needle, Pos + 1)
end
end.
-file("src/caffeine_query_language/parser.gleam", 229).
?DOC(" Finds the position of a substring in a string.\n").
-spec find_substring_position(binary(), binary()) -> gleam@option:option(integer()).
find_substring_position(Haystack, Needle) ->
find_substring_position_loop(Haystack, Needle, 0).
-file("src/caffeine_query_language/parser.gleam", 207).
-spec find_comparator_loop(binary(), list({binary(), comparator()})) -> {ok,
{binary(), comparator(), binary()}} |
{error, binary()}.
find_comparator_loop(Input, Comparators) ->
case Comparators of
[] ->
{error, <<"No comparator found in time_slice expression"/utf8>>};
[{Comp_str, Comp} | Rest] ->
case find_substring_position(Input, Comp_str) of
{some, Pos} ->
Query = gleam@string:slice(Input, 0, Pos),
Rest_start = Pos + string:length(Comp_str),
Rest_len = string:length(Input) - Rest_start,
Rest_str = gleam@string:slice(Input, Rest_start, Rest_len),
{ok, {Query, Comp, Rest_str}};
none ->
find_comparator_loop(Input, Rest)
end
end.
-file("src/caffeine_query_language/parser.gleam", 193).
?DOC(" Finds a comparator in the input and splits into (query, comparator, rest).\n").
-spec find_comparator(binary()) -> {ok, {binary(), comparator(), binary()}} |
{error, binary()}.
find_comparator(Input) ->
Comparators = [{<<">="/utf8>>, greater_than_or_equal_to},
{<<"<="/utf8>>, less_than_or_equal_to},
{<<">"/utf8>>, greater_than},
{<<"<"/utf8>>, less_than}],
find_comparator_loop(Input, Comparators).
-file("src/caffeine_query_language/parser.gleam", 253).
?DOC(" Splits on \"per\" keyword, returning (threshold_str, interval_str).\n").
-spec split_on_per(binary()) -> {ok, {binary(), binary()}} | {error, binary()}.
split_on_per(Input) ->
case find_substring_position(Input, <<"per"/utf8>>) of
{some, Pos} ->
Threshold_str = gleam@string:trim(gleam@string:slice(Input, 0, Pos)),
Rest_start = Pos + 3,
Rest_len = string:length(Input) - Rest_start,
Interval_str = gleam@string:trim(
gleam@string:slice(Input, Rest_start, Rest_len)
),
{ok, {Threshold_str, Interval_str}};
none ->
{error, <<"Missing 'per' keyword in time_slice expression"/utf8>>}
end.
-file("src/caffeine_query_language/parser.gleam", 288).
?DOC(" Parses an integer string as a float.\n").
-spec parse_int_as_float(binary()) -> {ok, float()} | {error, binary()}.
parse_int_as_float(Input) ->
case gleam_stdlib:contains_string(Input, <<"."/utf8>>) of
true ->
{error, <<"Not an integer"/utf8>>};
false ->
_pipe = gleam_stdlib:parse_float(<<Input/binary, ".0"/utf8>>),
gleam@result:map_error(
_pipe,
fun(_) -> <<"Invalid number"/utf8>> end
)
end.
-file("src/caffeine_query_language/parser.gleam", 268).
?DOC(" Parses a threshold value as a float.\n").
-spec parse_threshold(binary()) -> {ok, float()} | {error, binary()}.
parse_threshold(Input) ->
Trimmed = gleam@string:trim(Input),
case Trimmed of
<<""/utf8>> ->
{error, <<"Missing threshold in time_slice expression"/utf8>>};
_ ->
case gleam_stdlib:parse_float(Trimmed) of
{ok, F} ->
{ok, F};
{error, _} ->
case parse_int_as_float(Trimmed) of
{ok, F@1} ->
{ok, F@1};
{error, _} ->
{error,
<<<<"Invalid threshold '"/utf8, Trimmed/binary>>/binary,
"' in time_slice expression"/utf8>>}
end
end
end.
-file("src/caffeine_query_language/parser.gleam", 298).
?DOC(" Parses an interval like \"10s\", \"5m\", \"1h\", \"1.5h\" into seconds.\n").
-spec parse_interval(binary()) -> {ok, float()} | {error, binary()}.
parse_interval(Input) ->
Trimmed = gleam@string:trim(Input),
case Trimmed of
<<""/utf8>> ->
{error, <<"Missing interval in time_slice expression"/utf8>>};
_ ->
Len = string:length(Trimmed),
Unit = gleam@string:slice(Trimmed, Len - 1, 1),
Number_part = gleam@string:slice(Trimmed, 0, Len - 1),
gleam@result:'try'(case Unit of
<<"s"/utf8>> ->
{ok, 1.0};
<<"m"/utf8>> ->
{ok, 60.0};
<<"h"/utf8>> ->
{ok, 3600.0};
_ ->
{error,
<<<<"Invalid interval unit '"/utf8, Unit/binary>>/binary,
"' (expected s, m, or h)"/utf8>>}
end, fun(Multiplier) ->
gleam@result:'try'(
case gleam_stdlib:parse_float(Number_part) of
{ok, F} ->
{ok, F};
{error, _} ->
case parse_int_as_float(Number_part) of
{ok, F@1} ->
{ok, F@1};
{error, _} ->
{error,
<<<<"Invalid interval number '"/utf8,
Number_part/binary>>/binary,
"'"/utf8>>}
end
end,
fun(Number) -> {ok, Number * Multiplier} end
)
end)
end.
-file("src/caffeine_query_language/parser.gleam", 156).
?DOC(
" Parses the inner content of a time_slice expression.\n"
" Format: \"<query> <comparator> <threshold> per <interval>\"\n"
" Example: \"avg:system.cpu > 80 per 300s\"\n"
).
-spec parse_time_slice_spec(binary()) -> {ok, time_slice_exp()} |
{error, binary()}.
parse_time_slice_spec(Input) ->
Trimmed = gleam@string:trim(Input),
case Trimmed of
<<""/utf8>> ->
{error, <<"Empty time_slice expression"/utf8>>};
_ ->
gleam@result:'try'(
find_comparator(Trimmed),
fun(_use0) ->
{Query, Comparator, Rest} = _use0,
Query_trimmed = gleam@string:trim(Query),
case Query_trimmed of
<<""/utf8>> ->
{error,
<<"Missing query in time_slice expression"/utf8>>};
_ ->
gleam@result:'try'(
split_on_per(Rest),
fun(_use0@1) ->
{Threshold_str, Interval_str} = _use0@1,
gleam@result:'try'(
parse_threshold(Threshold_str),
fun(Threshold) ->
gleam@result:'try'(
parse_interval(Interval_str),
fun(Interval_seconds) ->
{ok,
{time_slice_exp,
Query_trimmed,
Comparator,
Threshold,
Interval_seconds}}
end
)
end
)
end
)
end
end
)
end.
-file("src/caffeine_query_language/parser.gleam", 136).
?DOC(
" Attempts to parse a keyword expression like \"time_slice(...)\".\n"
" Returns Error if the input is not a keyword expression.\n"
).
-spec try_parse_keyword_expr(binary()) -> {ok, exp()} | {error, binary()}.
try_parse_keyword_expr(Input) ->
case gleam_stdlib:string_starts_with(Input, <<"time_slice("/utf8>>) andalso gleam_stdlib:string_ends_with(
Input,
<<")"/utf8>>
) of
true ->
Prefix_len = string:length(<<"time_slice("/utf8>>),
Inner_len = (string:length(Input) - Prefix_len) - 1,
Inner = gleam@string:slice(Input, Prefix_len, Inner_len),
gleam@result:'try'(
parse_time_slice_spec(Inner),
fun(Spec) -> {ok, {time_slice_expr, Spec}} end
);
false ->
{error, <<"Not a keyword expression"/utf8>>}
end.
-file("src/caffeine_query_language/parser.gleam", 355).
?DOC(false).
-spec is_last_char(binary(), integer()) -> boolean().
is_last_char(Input, Pos) ->
Is_empty = gleam@string:is_empty(Input),
Is_last = Pos =:= (string:length(Input) - 1),
Is_empty orelse Is_last.
-file("src/caffeine_query_language/parser.gleam", 409).
-spec count_parens(integer(), binary(), integer()) -> integer().
count_parens(Cur_count, Input, Pos) ->
Char = gleam@string:slice(Input, Pos, 1),
case Char of
<<"("/utf8>> ->
Cur_count + 1;
<<")"/utf8>> ->
Cur_count - 1;
_ ->
Cur_count
end.
-file("src/caffeine_query_language/parser.gleam", 340).
?DOC(false).
-spec is_balanced_parens(binary(), integer(), integer()) -> boolean().
is_balanced_parens(Input, Pos, Count) ->
case Pos >= string:length(Input) of
true ->
Count =:= 0;
false ->
New_count = count_parens(Count, Input, Pos),
Does_not_close_too_early = not ((New_count =:= 0) andalso not is_last_char(
Input,
Pos
)),
Does_not_close_too_early andalso is_balanced_parens(
Input,
Pos + 1,
New_count
)
end.
-file("src/caffeine_query_language/parser.gleam", 91).
-spec is_fully_parenthesized(binary()) -> boolean().
is_fully_parenthesized(Input) ->
(gleam_stdlib:string_starts_with(Input, <<"("/utf8>>) andalso gleam_stdlib:string_ends_with(
Input,
<<")"/utf8>>
))
andalso ((string:length(Input) >= 2) andalso is_balanced_parens(Input, 1, 1)).
-file("src/caffeine_query_language/parser.gleam", 365).
?DOC(false).
-spec find_rightmost_operator_at_level(
binary(),
binary(),
integer(),
integer(),
integer()
) -> {ok, {binary(), binary()}} |
{error, caffeine_lang@common@errors:compilation_error()}.
find_rightmost_operator_at_level(
Input,
Operator,
Start_pos,
Paren_level,
Rightmost_pos
) ->
Operator_length = string:length(Operator),
case Start_pos >= string:length(Input) of
true ->
case Rightmost_pos of
-1 ->
{error, {c_q_l_parser_error, <<"Operator not found"/utf8>>}};
Pos ->
Left = gleam@string:trim(gleam@string:slice(Input, 0, Pos)),
Right_start = Pos + Operator_length,
Right_length = string:length(Input) - Right_start,
Right = gleam@string:trim(
gleam@string:slice(Input, Right_start, Right_length)
),
{ok, {Left, Right}}
end;
false ->
New_paren_level = count_parens(Paren_level, Input, Start_pos),
New_rightmost_pos = case (New_paren_level =:= 0) andalso (gleam@string:slice(
Input,
Start_pos,
Operator_length
)
=:= Operator) of
true ->
Start_pos;
false ->
Rightmost_pos
end,
find_rightmost_operator_at_level(
Input,
Operator,
Start_pos + 1,
New_paren_level,
New_rightmost_pos
)
end.
-file("src/caffeine_query_language/parser.gleam", 330).
-spec find_operator(binary(), binary()) -> {ok, {binary(), binary()}} |
{error, caffeine_lang@common@errors:compilation_error()}.
find_operator(Input, Operator) ->
find_rightmost_operator_at_level(Input, Operator, 0, 0, -1).
-file("src/caffeine_query_language/parser.gleam", 97).
-spec try_operators(binary(), list({binary(), operator()})) -> {ok, exp()} |
{error, binary()}.
try_operators(Input, Operators) ->
case Operators of
[] ->
case try_parse_keyword_expr(Input) of
{ok, Exp} ->
{ok, Exp};
{error, Err} ->
case gleam_stdlib:string_starts_with(
Input,
<<"time_slice("/utf8>>
)
andalso gleam_stdlib:string_ends_with(Input, <<")"/utf8>>) of
true ->
{error, Err};
false ->
Word = {word, Input},
{ok, {primary, {primary_word, Word}}}
end
end;
[{Op_str, Op} | Rest] ->
case find_operator(Input, Op_str) of
{ok, {Left, Right}} ->
gleam@result:'try'(
do_parse_expr(Left),
fun(Left_exp) ->
gleam@result:'try'(
do_parse_expr(Right),
fun(Right_exp) ->
{ok,
{operator_expr, Left_exp, Right_exp, Op}}
end
)
end
);
{error, _} ->
try_operators(Input, Rest)
end
end.
-file("src/caffeine_query_language/parser.gleam", 75).
?DOC(
" Parses a CQL expression string into an Exp AST node.\n"
" Handles parenthesized expressions and operator precedence.\n"
).
-spec do_parse_expr(binary()) -> {ok, exp()} | {error, binary()}.
do_parse_expr(Input) ->
Trimmed = gleam@string:trim(Input),
case is_fully_parenthesized(Trimmed) of
true ->
Inner = gleam@string:slice(Trimmed, 1, string:length(Trimmed) - 2),
gleam@result:'try'(
do_parse_expr(Inner),
fun(Inner_exp) -> {ok, {primary, {primary_exp, Inner_exp}}} end
);
false ->
Operators = [{<<"+"/utf8>>, add},
{<<"-"/utf8>>, sub},
{<<"*"/utf8>>, mul},
{<<"/"/utf8>>, 'div'}],
try_operators(Trimmed, Operators)
end.
-file("src/caffeine_query_language/parser.gleam", 68).
?DOC(false).
-spec parse_expr(binary()) -> {ok, exp_container()} | {error, binary()}.
parse_expr(Input) ->
gleam@result:'try'(
do_parse_expr(Input),
fun(Exp) -> {ok, {exp_container, Exp}} end
).