-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", 371). -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", 326). ?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(gleam@string:slice(Input, 0, Pos)), Right_start = Pos + Operator_length, Right_length = Input_len - 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_loop( Input, Operator, Start_pos + 1, New_paren_level, New_rightmost_pos, Operator_length, Input_len ) end. -file("src/caffeine_query_language/parser.gleam", 307). ?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, string:length(Operator), string:length(Input) ). -file("src/caffeine_query_language/parser.gleam", 275). -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", 220). ?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(<>), gleam@result:map_error( _pipe, fun(_) -> <<"Invalid number"/utf8>> end ) end ). -file("src/caffeine_query_language/parser.gleam", 230). ?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", 200). ?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", 164). -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( gleam@string:slice(Haystack, Pos, Needle_len) =:= 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", 154). ?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, string:length(Needle), string:length(Haystack) ). -file("src/caffeine_query_language/parser.gleam", 186). ?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", 132). -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 = 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", 119). ?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", 90). ?DOC( " Parses the inner content of a time_slice expression.\n" " Format: \" per \"\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", 73). ?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", 290). ?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", 285). ?DOC(false). -spec is_balanced_parens(binary(), integer(), integer()) -> boolean(). is_balanced_parens(Input, Pos, Count) -> is_balanced_parens_loop(Input, Pos, Count, string:length(Input)). -file("src/caffeine_query_language/parser.gleam", 33). -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", 39). -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", 17). ?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.