Packages
caffeine_lang
0.0.12
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_lang@cql@parser.erl
-module(caffeine_lang@cql@parser).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/caffeine_lang/cql/parser.gleam").
-export([debug_exp/1, is_balanced_parens/3, do_parse_expr/1, parse_expr/1]).
-export_type(['query'/0, exp_container/0, operator/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 exp() :: {operator_expr, exp(), exp(), operator()} | {primary, primary()}.
-type primary() :: {primary_word, word()} | {primary_exp, exp()}.
-type word() :: {word, binary()}.
-file("src/caffeine_lang/cql/parser.gleam", 39).
?DOC(
" Pretty print an expression tree for debugging.\n"
" Useful for visualizing the AST structure.\n"
).
-spec debug_exp(exp()) -> binary().
debug_exp(Exp) ->
case Exp of
{operator_expr, Left, Right, Op} ->
Op_str = case Op of
add ->
<<"+"/utf8>>;
sub ->
<<"-"/utf8>>;
mul ->
<<"*"/utf8>>;
'div' ->
<<"/"/utf8>>
end,
<<<<<<<<<<<<"("/utf8, (debug_exp(Left))/binary>>/binary, " "/utf8>>/binary,
Op_str/binary>>/binary,
" "/utf8>>/binary,
(debug_exp(Right))/binary>>/binary,
")"/utf8>>;
{primary, {primary_word, {word, Value}}} ->
Value;
{primary, {primary_exp, Inner}} ->
<<<<"("/utf8, (debug_exp(Inner))/binary>>/binary, ")"/utf8>>
end.
-file("src/caffeine_lang/cql/parser.gleam", 169).
?DOC(
" Check if parentheses are balanced from position `pos` with initial `count`.\n"
" \n"
" Args:\n"
" - input: The string to check\n"
" - pos: Starting position (0-indexed)\n"
" - count: Initial parenthesis depth (1 means we're inside one open paren)\n"
" \n"
" Returns True only if:\n"
" 1. Count reaches exactly 0 at the end of the string\n"
" 2. Count never reaches 0 before the end (no premature closing)\n"
" \n"
" This ensures that for \"(A + B)\", starting at pos=1 with count=1,\n"
" we verify the closing ')' is at the very end.\n"
).
-spec is_balanced_parens(binary(), integer(), integer()) -> boolean().
is_balanced_parens(Input, Pos, Count) ->
case Pos >= string:length(Input) of
true ->
Count =:= 0;
false ->
Char = gleam@string:slice(Input, Pos, 1),
New_count = case Char of
<<"("/utf8>> ->
Count + 1;
<<")"/utf8>> ->
Count - 1;
_ ->
Count
end,
case (New_count =:= 0) andalso (Pos < (string:length(Input) - 1)) of
true ->
false;
false ->
is_balanced_parens(Input, Pos + 1, New_count)
end
end.
-file("src/caffeine_lang/cql/parser.gleam", 105).
?DOC(
" Check if expression is fully wrapped in balanced parentheses.\n"
" \n"
" Returns True only if:\n"
" 1. Starts with '(' and ends with ')'\n"
" 2. The opening '(' matches the closing ')' (not an inner pair)\n"
" \n"
" Examples:\n"
" - \"(A + B)\" -> True\n"
" - \"(A) + (B)\" -> False (not fully wrapped)\n"
" - \"((A + B))\" -> True\n"
).
-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_lang/cql/parser.gleam", 204).
?DOC(
" Find rightmost occurrence of operator at parenthesis level 0.\n"
" \n"
" Args:\n"
" - input: Expression string to search\n"
" - operator: Operator string to find (e.g., \"+\", \"-\")\n"
" - start_pos: Current search position\n"
" - paren_level: Current parenthesis nesting depth\n"
" - rightmost_pos: Position of rightmost match found so far (-1 if none)\n"
" \n"
" Algorithm:\n"
" 1. Scan left-to-right, tracking parenthesis depth\n"
" 2. Record position whenever we find operator at level 0\n"
" 3. Return rightmost match (for left-associativity)\n"
" \n"
" Example: \"A + B + C\" finds the second '+' at level 0\n"
).
-spec find_rightmost_operator_at_level(
binary(),
binary(),
integer(),
integer(),
integer()
) -> {ok, {binary(), binary()}} | {error, binary()}.
find_rightmost_operator_at_level(
Input,
Operator,
Start_pos,
Paren_level,
Rightmost_pos
) ->
case Start_pos >= string:length(Input) of
true ->
case Rightmost_pos of
-1 ->
{error, <<"Operator not found"/utf8>>};
Pos ->
Left = gleam@string:trim(gleam@string:slice(Input, 0, Pos)),
Right_start = Pos + string:length(Operator),
Right_length = string:length(Input) - Right_start,
Right = gleam@string:trim(
gleam@string:slice(Input, Right_start, Right_length)
),
{ok, {Left, Right}}
end;
false ->
Char = gleam@string:slice(Input, Start_pos, 1),
New_paren_level = case Char of
<<"("/utf8>> ->
Paren_level + 1;
<<")"/utf8>> ->
Paren_level - 1;
_ ->
Paren_level
end,
Is_target_operator = (Paren_level =:= 0) andalso (gleam@string:slice(
Input,
Start_pos,
string:length(Operator)
)
=:= Operator),
New_rightmost_pos = case Is_target_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_lang/cql/parser.gleam", 149).
?DOC(
" Find the rightmost occurrence of operator at parenthesis level 0.\n"
" \n"
" We search for rightmost to handle left-associativity correctly.\n"
" Example: \"A + B + C\" should parse as \"(A + B) + C\"\n"
).
-spec find_operator(binary(), binary()) -> {ok, {binary(), binary()}} |
{error, binary()}.
find_operator(Input, Operator) ->
find_rightmost_operator_at_level(Input, Operator, 0, 0, -1).
-file("src/caffeine_lang/cql/parser.gleam", 119).
?DOC(
" Try to split expression by operators in order.\n"
" \n"
" Strategy:\n"
" 1. Try each operator in sequence (lowest precedence first)\n"
" 2. If operator found at top level, split and recursively parse both sides\n"
" 3. If no operator found, treat entire input as a word (leaf node)\n"
" \n"
" This builds the AST with correct precedence structure.\n"
).
-spec try_operators(binary(), list({binary(), operator()})) -> {ok, exp()} |
{error, binary()}.
try_operators(Input, Operators) ->
case Operators of
[] ->
Word = {word, Input},
{ok, {primary, {primary_word, Word}}};
[{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_lang/cql/parser.gleam", 76).
?DOC(
" Core parsing logic. Handles two cases:\n"
" 1. Fully parenthesized expressions: (expr) -> unwrap and parse inner\n"
" 2. Non-parenthesized: try to split by operators in precedence order\n"
" \n"
" Operator precedence (lowest to highest): +, -, *, /\n"
" We search for lowest precedence first to build correct AST structure.\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_lang/cql/parser.gleam", 65).
?DOC(
" Parse an expression string into an ExpContainer.\n"
" \n"
" Examples:\n"
" - \"A + B\" -> Addition of A and B\n"
" - \"(A + B) / C\" -> Division with parenthesized addition\n"
" - \"A * B + C / D - E\" -> Mixed operators with precedence\n"
).
-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
).