Current section

Files

Jump to
caffeine_lang src caffeine_query_language@parser.erl
Raw

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([find_rightmost_operator_at_level/5, is_balanced_parens/3, parse_expr/1]).
-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_query_language/parser.gleam", 401).
-spec count_parens(integer(), binary(), integer()) -> integer().
count_parens(Cur_count, Input, Pos) ->
case parser_ffi:code_unit_at(Input, Pos) of
C when C =:= 16#28 ->
Cur_count + 1;
C@1 when C@1 =:= 16#29 ->
Cur_count - 1;
_ ->
Cur_count
end.
-file("src/caffeine_query_language/parser.gleam", 357).
?DOC(" Internal loop with pre-computed lengths.\n").
-spec find_rightmost_operator_at_level_loop(
binary(),
binary(),
integer(),
integer(),
integer(),
integer(),
integer()
) -> {ok, {binary(), binary()}} |
{error, caffeine_lang@errors:compilation_error()}.
find_rightmost_operator_at_level_loop(
Input,
Operator,
Start_pos,
Paren_level,
Rightmost_pos,
Operator_length,
Input_len
) ->
case Start_pos >= Input_len of
true ->
case Rightmost_pos of
-1 ->
{error,
caffeine_lang@errors:cql_parser_error(
<<"Operator not found"/utf8>>
)};
Pos ->
Left = gleam@string:trim(
parser_ffi:slice_codeunits(Input, 0, Pos)
),
Right_start = Pos + Operator_length,
Right_length = Input_len - Right_start,
Right = gleam@string:trim(
parser_ffi:slice_codeunits(
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 parser_ffi:substring_equals_at(
Input,
Start_pos,
Operator
) of
true ->
Start_pos;
false ->
Rightmost_pos
end,
find_rightmost_operator_at_level_loop(
Input,
Operator,
Start_pos + 1,
New_paren_level,
New_rightmost_pos,
Operator_length,
Input_len
)
end.
-file("src/caffeine_query_language/parser.gleam", 338).
?DOC(false).
-spec find_rightmost_operator_at_level(
binary(),
binary(),
integer(),
integer(),
integer()
) -> {ok, {binary(), binary()}} |
{error, caffeine_lang@errors:compilation_error()}.
find_rightmost_operator_at_level(
Input,
Operator,
Start_pos,
Paren_level,
Rightmost_pos
) ->
find_rightmost_operator_at_level_loop(
Input,
Operator,
Start_pos,
Paren_level,
Rightmost_pos,
parser_ffi:code_unit_length(Operator),
parser_ffi:code_unit_length(Input)
).
-file("src/caffeine_query_language/parser.gleam", 306).
-spec find_operator(binary(), binary()) -> {ok, {binary(), binary()}} |
{error, caffeine_lang@errors:compilation_error()}.
find_operator(Input, Operator) ->
find_rightmost_operator_at_level(Input, Operator, 0, 0, -1).
-file("src/caffeine_query_language/parser.gleam", 251).
?DOC(" Parses an integer string as a float.\n").
-spec parse_int_as_float(binary()) -> {ok, float()} | {error, binary()}.
parse_int_as_float(Input) ->
gleam@bool:guard(
gleam_stdlib:contains_string(Input, <<"."/utf8>>),
{error, <<"Not an integer"/utf8>>},
fun() -> _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", 261).
?DOC(" Parses an interval like \"10s\", \"5m\", \"1h\", \"500ms\", \"1d\" 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, Number_part} = case (Len >= 3) andalso (gleam@string:slice(
Trimmed,
Len - 2,
2
)
=:= <<"ms"/utf8>>) of
true ->
{<<"ms"/utf8>>, gleam@string:slice(Trimmed, 0, Len - 2)};
false ->
{gleam@string:slice(Trimmed, Len - 1, 1),
gleam@string:slice(Trimmed, 0, Len - 1)}
end,
gleam@result:'try'(case Unit of
<<"ms"/utf8>> ->
{ok, 0.001};
<<"s"/utf8>> ->
{ok, 1.0};
<<"m"/utf8>> ->
{ok, 60.0};
<<"h"/utf8>> ->
{ok, 3600.0};
<<"d"/utf8>> ->
{ok, 86400.0};
_ ->
{error,
<<<<"Invalid interval unit '"/utf8, Unit/binary>>/binary,
"' (expected ms, s, m, h, or d)"/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", 231).
?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", 194).
-spec find_substring_position_loop(
binary(),
binary(),
integer(),
integer(),
integer()
) -> gleam@option:option(integer()).
find_substring_position_loop(Haystack, Needle, Pos, Needle_len, Haystack_len) ->
gleam@bool:guard(
(Pos + Needle_len) > Haystack_len,
none,
fun() ->
gleam@bool:guard(
parser_ffi:substring_equals_at(Haystack, Pos, Needle),
{some, Pos},
fun() ->
find_substring_position_loop(
Haystack,
Needle,
Pos + 1,
Needle_len,
Haystack_len
)
end
)
end
).
-file("src/caffeine_query_language/parser.gleam", 184).
?DOC(" Finds the position of a substring in a string. Returns a codeunit position.\n").
-spec find_substring_position(binary(), binary()) -> gleam@option:option(integer()).
find_substring_position(Haystack, Needle) ->
find_substring_position_loop(
Haystack,
Needle,
0,
parser_ffi:code_unit_length(Needle),
parser_ffi:code_unit_length(Haystack)
).
-file("src/caffeine_query_language/parser.gleam", 216).
?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(
parser_ffi:slice_codeunits(Input, 0, Pos)
),
Rest_start = Pos + 3,
Rest_len = parser_ffi:code_unit_length(Input) - Rest_start,
Interval_str = gleam@string:trim(
parser_ffi:slice_codeunits(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", 162).
-spec find_comparator_loop(
binary(),
list({binary(), caffeine_query_language@ast:comparator()})
) -> {ok, {binary(), caffeine_query_language@ast: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 = parser_ffi:slice_codeunits(Input, 0, Pos),
Rest_start = Pos + parser_ffi:code_unit_length(Comp_str),
Rest_len = parser_ffi:code_unit_length(Input) - Rest_start,
Rest_str = parser_ffi:slice_codeunits(
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", 149).
?DOC(" Finds a comparator in the input and splits into (query, comparator, rest).\n").
-spec find_comparator(binary()) -> {ok,
{binary(), caffeine_query_language@ast: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", 120).
?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,
caffeine_query_language@ast: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", 103).
?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,
caffeine_query_language@ast:exp(caffeine_query_language@ast:cql_parsed())} |
{error, binary()}.
try_parse_keyword_expr(Input) ->
gleam@bool:guard(
not (gleam_stdlib:string_starts_with(Input, <<"time_slice("/utf8>>)
andalso gleam_stdlib:string_ends_with(Input, <<")"/utf8>>)),
{error, <<"Not a keyword expression"/utf8>>},
fun() ->
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
)
end
).
-file("src/caffeine_query_language/parser.gleam", 321).
?DOC(" Internal loop with pre-computed input length.\n").
-spec is_balanced_parens_loop(binary(), integer(), integer(), integer()) -> boolean().
is_balanced_parens_loop(Input, Pos, Count, Input_len) ->
gleam@bool:guard(
Pos >= Input_len,
Count =:= 0,
fun() ->
New_count = count_parens(Count, Input, Pos),
Does_not_close_too_early = not ((New_count =:= 0) andalso (Pos /= (Input_len
- 1))),
Does_not_close_too_early andalso is_balanced_parens_loop(
Input,
Pos + 1,
New_count,
Input_len
)
end
).
-file("src/caffeine_query_language/parser.gleam", 316).
?DOC(false).
-spec is_balanced_parens(binary(), integer(), integer()) -> boolean().
is_balanced_parens(Input, Pos, Count) ->
is_balanced_parens_loop(
Input,
Pos,
Count,
parser_ffi:code_unit_length(Input)
).
-file("src/caffeine_query_language/parser.gleam", 63).
-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", 69).
-spec try_operators(
binary(),
list({binary(), caffeine_query_language@ast:operator()})
) -> {ok,
caffeine_query_language@ast:exp(caffeine_query_language@ast:cql_parsed())} |
{error, binary()}.
try_operators(Input, Operators) ->
case Operators of
[] ->
case try_parse_keyword_expr(Input) of
{ok, Exp} ->
{ok, Exp};
{error, Err} ->
gleam@bool:guard(
gleam_stdlib:string_starts_with(
Input,
<<"time_slice("/utf8>>
)
andalso gleam_stdlib:string_ends_with(
Input,
<<")"/utf8>>
),
{error, Err},
fun() ->
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'(
parse_expr(Left),
fun(Left_exp) ->
gleam@result:'try'(
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", 47).
?DOC(false).
-spec parse_expr(binary()) -> {ok,
caffeine_query_language@ast:exp(caffeine_query_language@ast:cql_parsed())} |
{error, binary()}.
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'(
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.