Current section

Files

Jump to
dream_test src dream_test@gherkin@step_trie.erl
Raw

src/dream_test@gherkin@step_trie.erl

-module(dream_test@gherkin@step_trie).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/dream_test/gherkin/step_trie.gleam").
-export([new/0, parse_step_pattern/1, insert/4, tokenize_step_text/1, lookup/3]).
-export_type([step_segment/0, captured_value/0, step_trie_node/1, step_trie/1, step_match/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.
?MODULEDOC(
" Fast step-definition lookup using Cucumber-Expression-style placeholders.\n"
"\n"
" This is the data structure behind `dream_test/gherkin/steps`. It matches step\n"
" text in time proportional to the **number of tokens in the step text**, not\n"
" the number of registered step definitions.\n"
"\n"
" ## Placeholder syntax\n"
"\n"
" - `{int}`: integers like `42`, `-5`\n"
" - `{float}`: decimals like `3.14`, `-0.5`\n"
" - `{string}`: quoted strings like `\"hello world\"`\n"
" - `{word}` / `{}`: a single token\n"
"\n"
" Prefix/suffix text can be attached to placeholders. For example,\n"
" `${float}USD` matches `$19.99USD` and captures `19.99` as a float.\n"
"\n"
" ## Example\n"
"\n"
" ```gleam\n"
" step_trie.lookup(trie, \"Then\", \"the total is $19.99USD\")\n"
" |> should\n"
" |> be_equal(\n"
" Some(\n"
" step_trie.StepMatch(handler: \"total_usd\", captures: [\n"
" step_trie.CapturedFloat(19.99),\n"
" ]),\n"
" ),\n"
" )\n"
" |> or_fail_with(\"expected float capture for $19.99USD\")\n"
" ```\n"
).
-type step_segment() :: {literal_word, binary()} |
int_param |
float_param |
string_param |
word_param |
any_param.
-type captured_value() :: {captured_int, integer()} |
{captured_float, float()} |
{captured_string, binary()} |
{captured_word, binary()}.
-opaque step_trie_node(FRV) :: {step_trie_node,
gleam@dict:dict(binary(), FRV),
gleam@dict:dict(binary(), step_trie_node(FRV)),
gleam@option:option(step_trie_node(FRV)),
gleam@option:option(step_trie_node(FRV)),
gleam@option:option(step_trie_node(FRV)),
gleam@option:option(step_trie_node(FRV)),
gleam@option:option(step_trie_node(FRV))}.
-opaque step_trie(FRW) :: {step_trie, step_trie_node(FRW)}.
-type step_match(FRX) :: {step_match, FRX, list(captured_value())}.
-file("src/dream_test/gherkin/step_trie.gleam", 128).
?DOC(" Create an empty trie node.\n").
-spec empty_node() -> step_trie_node(any()).
empty_node() ->
{step_trie_node, maps:new(), maps:new(), none, none, none, none, none}.
-file("src/dream_test/gherkin/step_trie.gleam", 186).
?DOC(
" Create a new empty step trie.\n"
"\n"
" Returns a trie with no step definitions.\n"
"\n"
" ## Example\n"
"\n"
" ```gleam\n"
" let trie =\n"
" step_trie.new()\n"
" |> step_trie.insert(\n"
" keyword: \"Given\",\n"
" pattern: \"I have an empty cart\",\n"
" handler: \"empty\",\n"
" )\n"
" ```\n"
).
-spec new() -> step_trie(any()).
new() ->
{step_trie, empty_node()}.
-file("src/dream_test/gherkin/step_trie.gleam", 318).
-spec get_or_create_literal_child(step_trie_node(FTK), binary()) -> step_trie_node(FTK).
get_or_create_literal_child(Node, Word) ->
case gleam_stdlib:map_get(erlang:element(3, Node), Word) of
{ok, Existing} ->
Existing;
{error, _} ->
empty_node()
end.
-file("src/dream_test/gherkin/step_trie.gleam", 241).
-spec insert_handler_at_node(step_trie_node(FSJ), binary(), FSJ) -> step_trie_node(FSJ).
insert_handler_at_node(Node, Keyword, Handler) ->
Updated_handlers = gleam@dict:insert(
erlang:element(2, Node),
Keyword,
Handler
),
{step_trie_node,
Updated_handlers,
erlang:element(3, Node),
erlang:element(4, Node),
erlang:element(5, Node),
erlang:element(6, Node),
erlang:element(7, Node),
erlang:element(8, Node)}.
-file("src/dream_test/gherkin/step_trie.gleam", 307).
-spec insert_any_segment(
step_trie_node(FTG),
binary(),
list(step_segment()),
FTG
) -> step_trie_node(FTG).
insert_any_segment(Node, Keyword, Rest, Handler) ->
Child = gleam@option:unwrap(erlang:element(8, Node), empty_node()),
Updated_child = insert_into_node(Child, Keyword, Rest, Handler),
{step_trie_node,
erlang:element(2, Node),
erlang:element(3, Node),
erlang:element(4, Node),
erlang:element(5, Node),
erlang:element(6, Node),
erlang:element(7, Node),
{some, Updated_child}}.
-file("src/dream_test/gherkin/step_trie.gleam", 296).
-spec insert_word_segment(
step_trie_node(FTC),
binary(),
list(step_segment()),
FTC
) -> step_trie_node(FTC).
insert_word_segment(Node, Keyword, Rest, Handler) ->
Child = gleam@option:unwrap(erlang:element(7, Node), empty_node()),
Updated_child = insert_into_node(Child, Keyword, Rest, Handler),
{step_trie_node,
erlang:element(2, Node),
erlang:element(3, Node),
erlang:element(4, Node),
erlang:element(5, Node),
erlang:element(6, Node),
{some, Updated_child},
erlang:element(8, Node)}.
-file("src/dream_test/gherkin/step_trie.gleam", 285).
-spec insert_string_segment(
step_trie_node(FSY),
binary(),
list(step_segment()),
FSY
) -> step_trie_node(FSY).
insert_string_segment(Node, Keyword, Rest, Handler) ->
Child = gleam@option:unwrap(erlang:element(4, Node), empty_node()),
Updated_child = insert_into_node(Child, Keyword, Rest, Handler),
{step_trie_node,
erlang:element(2, Node),
erlang:element(3, Node),
{some, Updated_child},
erlang:element(5, Node),
erlang:element(6, Node),
erlang:element(7, Node),
erlang:element(8, Node)}.
-file("src/dream_test/gherkin/step_trie.gleam", 274).
-spec insert_float_segment(
step_trie_node(FSU),
binary(),
list(step_segment()),
FSU
) -> step_trie_node(FSU).
insert_float_segment(Node, Keyword, Rest, Handler) ->
Child = gleam@option:unwrap(erlang:element(6, Node), empty_node()),
Updated_child = insert_into_node(Child, Keyword, Rest, Handler),
{step_trie_node,
erlang:element(2, Node),
erlang:element(3, Node),
erlang:element(4, Node),
erlang:element(5, Node),
{some, Updated_child},
erlang:element(7, Node),
erlang:element(8, Node)}.
-file("src/dream_test/gherkin/step_trie.gleam", 263).
-spec insert_int_segment(
step_trie_node(FSQ),
binary(),
list(step_segment()),
FSQ
) -> step_trie_node(FSQ).
insert_int_segment(Node, Keyword, Rest, Handler) ->
Child = gleam@option:unwrap(erlang:element(5, Node), empty_node()),
Updated_child = insert_into_node(Child, Keyword, Rest, Handler),
{step_trie_node,
erlang:element(2, Node),
erlang:element(3, Node),
erlang:element(4, Node),
{some, Updated_child},
erlang:element(6, Node),
erlang:element(7, Node),
erlang:element(8, Node)}.
-file("src/dream_test/gherkin/step_trie.gleam", 250).
-spec insert_literal_segment(
step_trie_node(FSM),
binary(),
binary(),
list(step_segment()),
FSM
) -> step_trie_node(FSM).
insert_literal_segment(Node, Word, Keyword, Rest, Handler) ->
Child = get_or_create_literal_child(Node, Word),
Updated_child = insert_into_node(Child, Keyword, Rest, Handler),
Updated_children = gleam@dict:insert(
erlang:element(3, Node),
Word,
Updated_child
),
{step_trie_node,
erlang:element(2, Node),
Updated_children,
erlang:element(4, Node),
erlang:element(5, Node),
erlang:element(6, Node),
erlang:element(7, Node),
erlang:element(8, Node)}.
-file("src/dream_test/gherkin/step_trie.gleam", 223).
-spec insert_into_node(step_trie_node(FSF), binary(), list(step_segment()), FSF) -> step_trie_node(FSF).
insert_into_node(Node, Keyword, Segments, Handler) ->
case Segments of
[] ->
insert_handler_at_node(Node, Keyword, Handler);
[{literal_word, Word} | Rest] ->
insert_literal_segment(Node, Word, Keyword, Rest, Handler);
[int_param | Rest@1] ->
insert_int_segment(Node, Keyword, Rest@1, Handler);
[float_param | Rest@2] ->
insert_float_segment(Node, Keyword, Rest@2, Handler);
[string_param | Rest@3] ->
insert_string_segment(Node, Keyword, Rest@3, Handler);
[word_param | Rest@4] ->
insert_word_segment(Node, Keyword, Rest@4, Handler);
[any_param | Rest@5] ->
insert_any_segment(Node, Keyword, Rest@5, Handler)
end.
-file("src/dream_test/gherkin/step_trie.gleam", 390).
-spec parse_pattern_word(binary()) -> step_segment().
parse_pattern_word(Word) ->
case Word of
<<"{int}"/utf8>> ->
int_param;
<<"{float}"/utf8>> ->
float_param;
<<"{string}"/utf8>> ->
string_param;
<<"{word}"/utf8>> ->
word_param;
<<"{}"/utf8>> ->
any_param;
_ ->
{literal_word, Word}
end.
-file("src/dream_test/gherkin/step_trie.gleam", 460).
-spec append_list(list(binary()), list(binary())) -> list(binary()).
append_list(Parts, Extra) ->
lists:append(Parts, Extra).
-file("src/dream_test/gherkin/step_trie.gleam", 446).
-spec append_if_non_empty(list(binary()), binary()) -> list(binary()).
append_if_non_empty(Parts, Value) ->
case Value of
<<""/utf8>> ->
Parts;
_ ->
lists:append(Parts, [Value])
end.
-file("src/dream_test/gherkin/step_trie.gleam", 453).
-spec split_after(binary()) -> list(binary()).
split_after(After) ->
case After of
<<""/utf8>> ->
[];
_ ->
split_word_around_placeholder(After)
end.
-file("src/dream_test/gherkin/step_trie.gleam", 435).
-spec append_placeholder_parts(binary(), binary(), binary()) -> list(binary()).
append_placeholder_parts(Before, Placeholder, After) ->
_pipe = [],
_pipe@1 = append_if_non_empty(_pipe, Before),
_pipe@2 = lists:append(_pipe@1, [Placeholder]),
append_list(_pipe@2, split_after(After)).
-file("src/dream_test/gherkin/step_trie.gleam", 424).
-spec try_split_on_placeholder(binary(), binary()) -> gleam@option:option(list(binary())).
try_split_on_placeholder(Word, Placeholder) ->
case gleam@string:split_once(Word, Placeholder) of
{ok, {Before, After}} ->
{some, append_placeholder_parts(Before, Placeholder, After)};
{error, _} ->
none
end.
-file("src/dream_test/gherkin/step_trie.gleam", 410).
-spec split_on_first_placeholder(binary(), list(binary())) -> list(binary()).
split_on_first_placeholder(Word, Placeholders) ->
case Placeholders of
[] ->
[Word];
[Placeholder | Rest] ->
case try_split_on_placeholder(Word, Placeholder) of
{some, Parts} ->
Parts;
none ->
split_on_first_placeholder(Word, Rest)
end
end.
-file("src/dream_test/gherkin/step_trie.gleam", 405).
?DOC(
" Split a word around any placeholder it contains.\n"
" e.g., \"${float}\" -> [\"$\", \"{float}\"]\n"
" e.g., \"{int}%\" -> [\"{int}\", \"%\"]\n"
" e.g., \"hello\" -> [\"hello\"]\n"
).
-spec split_word_around_placeholder(binary()) -> list(binary()).
split_word_around_placeholder(Word) ->
Placeholders = [<<"{int}"/utf8>>,
<<"{float}"/utf8>>,
<<"{string}"/utf8>>,
<<"{word}"/utf8>>,
<<"{}"/utf8>>],
split_on_first_placeholder(Word, Placeholders).
-file("src/dream_test/gherkin/step_trie.gleam", 386).
-spec is_non_empty(binary()) -> boolean().
is_non_empty(Word) ->
Word /= <<""/utf8>>.
-file("src/dream_test/gherkin/step_trie.gleam", 378).
?DOC(
" Parse a step pattern string into segments.\n"
"\n"
" Splits the pattern by whitespace and converts each token into a `StepSegment`.\n"
" Placeholders like `{int}` become typed parameter segments, while other tokens\n"
" become literal word segments.\n"
"\n"
" ## Placeholder Syntax\n"
"\n"
" | Placeholder | Segment Type | Matches |\n"
" |-------------|--------------|---------|\n"
" | `{int}` | `IntParam` | Integers like `42`, `-5` |\n"
" | `{float}` | `FloatParam` | Decimals like `3.14` |\n"
" | `{string}` | `StringParam` | Quoted strings like `\"hello\"` |\n"
" | `{word}` | `WordParam` | Single unquoted words |\n"
" | `{}` | `AnyParam` | Any single token |\n"
"\n"
" ## Prefix and Suffix Handling\n"
"\n"
" Placeholders can have literal prefixes and/or suffixes attached. The parser\n"
" automatically splits these into separate segments:\n"
"\n"
" - `${float}` → `[LiteralWord(\"$\"), FloatParam]`\n"
" - `{int}%` → `[IntParam, LiteralWord(\"%\")]`\n"
" - `${float}USD` → `[LiteralWord(\"$\"), FloatParam, LiteralWord(\"USD\")]`\n"
"\n"
" This enables patterns like `\"the price is ${float}\"` to match text like\n"
" `\"the price is $19.99\"` and capture `19.99` as the float value.\n"
"\n"
" ## Example\n"
"\n"
" ```gleam\n"
" step_trie.parse_step_pattern(\"the total is ${float}USD\")\n"
" |> should\n"
" |> be_equal([\n"
" step_trie.LiteralWord(\"the\"),\n"
" step_trie.LiteralWord(\"total\"),\n"
" step_trie.LiteralWord(\"is\"),\n"
" step_trie.LiteralWord(\"$\"),\n"
" step_trie.FloatParam,\n"
" step_trie.LiteralWord(\"USD\"),\n"
" ])\n"
" |> or_fail_with(\n"
" \"expected ${float}USD to split into literal + FloatParam segments\",\n"
" )\n"
" ```\n"
).
-spec parse_step_pattern(binary()) -> list(step_segment()).
parse_step_pattern(Pattern) ->
_pipe = Pattern,
_pipe@1 = gleam@string:split(_pipe, <<" "/utf8>>),
_pipe@2 = gleam@list:filter(_pipe@1, fun is_non_empty/1),
_pipe@3 = gleam@list:flat_map(_pipe@2, fun split_word_around_placeholder/1),
gleam@list:map(_pipe@3, fun parse_pattern_word/1).
-file("src/dream_test/gherkin/step_trie.gleam", 212).
?DOC(
" Insert a step pattern into the trie.\n"
"\n"
" Adds a handler at the path specified by the pattern. If a handler already\n"
" exists for the same keyword and pattern, it is replaced.\n"
"\n"
" ## Parameters\n"
"\n"
" - `trie`: The trie to insert into\n"
" - `keyword`: Step keyword as string (\"Given\", \"When\", \"Then\", or \"*\" for any)\n"
" - `pattern`: Step pattern with placeholders (e.g., \"I have {int} items\")\n"
" - `handler`: The handler to store\n"
"\n"
" ## Example\n"
"\n"
" ```gleam\n"
" |> step_trie.insert(\n"
" keyword: \"Given\",\n"
" pattern: \"I have {int} items\",\n"
" handler: \"count\",\n"
" )\n"
" ```\n"
).
-spec insert(step_trie(FSC), binary(), binary(), FSC) -> step_trie(FSC).
insert(Trie, Keyword, Pattern, Handler) ->
Segments = parse_step_pattern(Pattern),
Updated_root = insert_into_node(
erlang:element(2, Trie),
Keyword,
Segments,
Handler
),
{step_trie, Updated_root}.
-file("src/dream_test/gherkin/step_trie.gleam", 483).
-spec split_numeric_with_regex(binary(), gleam@regexp:regexp()) -> list(binary()).
split_numeric_with_regex(Token, Re) ->
case gleam@regexp:split(Re, Token) of
[Only] ->
[Only];
Parts ->
_pipe = Parts,
gleam@list:filter(_pipe, fun is_non_empty/1)
end.
-file("src/dream_test/gherkin/step_trie.gleam", 476).
-spec do_split_numeric(binary()) -> list(binary()).
do_split_numeric(Token) ->
case gleam@regexp:from_string(<<"(-?[0-9]+\\.?[0-9]*)"/utf8>>) of
{ok, Re} ->
split_numeric_with_regex(Token, Re);
{error, _} ->
[Token]
end.
-file("src/dream_test/gherkin/step_trie.gleam", 801).
-spec is_quoted_string(binary()) -> boolean().
is_quoted_string(Word) ->
gleam_stdlib:string_starts_with(Word, <<"\""/utf8>>) andalso gleam_stdlib:string_ends_with(
Word,
<<"\""/utf8>>
).
-file("src/dream_test/gherkin/step_trie.gleam", 469).
?DOC(
" Split a token on boundaries between numeric and non-numeric characters.\n"
" e.g., \"$3.00\" -> [\"$\", \"3.00\"]\n"
" e.g., \"10%\" -> [\"10\", \"%\"]\n"
" e.g., \"hello\" -> [\"hello\"]\n"
" Quoted strings are not split.\n"
).
-spec split_numeric_boundaries(binary()) -> list(binary()).
split_numeric_boundaries(Token) ->
case is_quoted_string(Token) of
true ->
[Token];
false ->
do_split_numeric(Token)
end.
-file("src/dream_test/gherkin/step_trie.gleam", 805).
-spec unquote_string(binary()) -> binary().
unquote_string(Quoted) ->
_pipe = Quoted,
_pipe@1 = gleam@string:drop_start(_pipe, 1),
gleam@string:drop_end(_pipe@1, 1).
-file("src/dream_test/gherkin/step_trie.gleam", 636).
-spec lookup_handler_at_node(
step_trie_node(FUW),
binary(),
list(captured_value())
) -> gleam@option:option(step_match(FUW)).
lookup_handler_at_node(Node, Keyword, Captures) ->
Handler_result = case gleam_stdlib:map_get(erlang:element(2, Node), Keyword) of
{ok, Handler} ->
{some, Handler};
{error, _} ->
_pipe = gleam_stdlib:map_get(erlang:element(2, Node), <<"*"/utf8>>),
gleam@option:from_result(_pipe)
end,
case Handler_result of
{some, Handler@1} ->
{some, {step_match, Handler@1, lists:reverse(Captures)}};
none ->
none
end.
-file("src/dream_test/gherkin/step_trie.gleam", 784).
-spec try_any_match(
step_trie_node(FWX),
binary(),
binary(),
list(binary()),
list(captured_value())
) -> gleam@option:option(step_match(FWX)).
try_any_match(Node, Keyword, Word, Rest, Captures) ->
case erlang:element(8, Node) of
{some, Child} ->
Updated_captures = [{captured_word, Word} | Captures],
lookup_in_node(Child, Keyword, Rest, Updated_captures);
none ->
none
end.
-file("src/dream_test/gherkin/step_trie.gleam", 767).
-spec try_word_matches(
step_trie_node(FWR),
binary(),
binary(),
list(binary()),
list(captured_value())
) -> gleam@option:option(step_match(FWR)).
try_word_matches(Node, Keyword, Word, Rest, Captures) ->
case erlang:element(7, Node) of
{some, Child} ->
Updated_captures = [{captured_word, Word} | Captures],
lookup_in_node(Child, Keyword, Rest, Updated_captures);
none ->
try_any_match(Node, Keyword, Word, Rest, Captures)
end.
-file("src/dream_test/gherkin/step_trie.gleam", 751).
-spec try_float_match(
step_trie_node(FWL),
binary(),
binary(),
list(binary()),
list(captured_value())
) -> gleam@option:option(step_match(FWL)).
try_float_match(Node, Keyword, Word, Rest, Captures) ->
case {erlang:element(6, Node), gleam_stdlib:parse_float(Word)} of
{{some, Child}, {ok, Value}} ->
Updated_captures = [{captured_float, Value} | Captures],
lookup_in_node(Child, Keyword, Rest, Updated_captures);
{_, _} ->
none
end.
-file("src/dream_test/gherkin/step_trie.gleam", 737).
-spec try_float_then_word(
step_trie_node(FWF),
binary(),
binary(),
list(binary()),
list(captured_value())
) -> gleam@option:option(step_match(FWF)).
try_float_then_word(Node, Keyword, Word, Rest, Captures) ->
case try_float_match(Node, Keyword, Word, Rest, Captures) of
{some, Result} ->
{some, Result};
none ->
try_word_matches(Node, Keyword, Word, Rest, Captures)
end.
-file("src/dream_test/gherkin/step_trie.gleam", 721).
-spec try_int_match(
step_trie_node(FVZ),
binary(),
binary(),
list(binary()),
list(captured_value())
) -> gleam@option:option(step_match(FVZ)).
try_int_match(Node, Keyword, Word, Rest, Captures) ->
case {erlang:element(5, Node), gleam_stdlib:parse_int(Word)} of
{{some, Child}, {ok, Value}} ->
Updated_captures = [{captured_int, Value} | Captures],
lookup_in_node(Child, Keyword, Rest, Updated_captures);
{_, _} ->
none
end.
-file("src/dream_test/gherkin/step_trie.gleam", 707).
-spec try_numeric_matches(
step_trie_node(FVT),
binary(),
binary(),
list(binary()),
list(captured_value())
) -> gleam@option:option(step_match(FVT)).
try_numeric_matches(Node, Keyword, Word, Rest, Captures) ->
case try_int_match(Node, Keyword, Word, Rest, Captures) of
{some, Result} ->
{some, Result};
none ->
try_float_then_word(Node, Keyword, Word, Rest, Captures)
end.
-file("src/dream_test/gherkin/step_trie.gleam", 690).
-spec try_string_match(
step_trie_node(FVN),
binary(),
binary(),
list(binary()),
list(captured_value())
) -> gleam@option:option(step_match(FVN)).
try_string_match(Node, Keyword, Word, Rest, Captures) ->
case {erlang:element(4, Node), is_quoted_string(Word)} of
{{some, Child}, true} ->
Unquoted = unquote_string(Word),
Updated_captures = [{captured_string, Unquoted} | Captures],
lookup_in_node(Child, Keyword, Rest, Updated_captures);
{_, _} ->
none
end.
-file("src/dream_test/gherkin/step_trie.gleam", 676).
-spec try_param_matches(
step_trie_node(FVH),
binary(),
binary(),
list(binary()),
list(captured_value())
) -> gleam@option:option(step_match(FVH)).
try_param_matches(Node, Keyword, Word, Rest, Captures) ->
case try_string_match(Node, Keyword, Word, Rest, Captures) of
{some, Result} ->
{some, Result};
none ->
try_numeric_matches(Node, Keyword, Word, Rest, Captures)
end.
-file("src/dream_test/gherkin/step_trie.gleam", 662).
?DOC(
" Match a single word against the node's children.\n"
"\n"
" Priority order:\n"
" 1. Literal match (exact word)\n"
" 2. {string} (quoted string)\n"
" 3. {int} (integer)\n"
" 4. {float} (decimal)\n"
" 5. {word} (unquoted single word)\n"
" 6. {} (any word)\n"
).
-spec lookup_word(
step_trie_node(FVB),
binary(),
binary(),
list(binary()),
list(captured_value())
) -> gleam@option:option(step_match(FVB)).
lookup_word(Node, Keyword, Word, Rest, Captures) ->
case gleam_stdlib:map_get(erlang:element(3, Node), Word) of
{ok, Child} ->
lookup_in_node(Child, Keyword, Rest, Captures);
{error, _} ->
try_param_matches(Node, Keyword, Word, Rest, Captures)
end.
-file("src/dream_test/gherkin/step_trie.gleam", 624).
-spec lookup_in_node(
step_trie_node(FUQ),
binary(),
list(binary()),
list(captured_value())
) -> gleam@option:option(step_match(FUQ)).
lookup_in_node(Node, Keyword, Words, Captures) ->
case Words of
[] ->
lookup_handler_at_node(Node, Keyword, Captures);
[Word | Rest] ->
lookup_word(Node, Keyword, Word, Rest, Captures)
end.
-file("src/dream_test/gherkin/step_trie.gleam", 617).
-spec add_token_if_non_empty(list(binary()), binary()) -> list(binary()).
add_token_if_non_empty(Tokens, Token) ->
case Token of
<<""/utf8>> ->
Tokens;
_ ->
[Token | Tokens]
end.
-file("src/dream_test/gherkin/step_trie.gleam", 612).
-spec finalize_tokens(list(binary()), binary()) -> list(binary()).
finalize_tokens(Tokens, Current) ->
_pipe = add_token_if_non_empty(Tokens, Current),
lists:reverse(_pipe).
-file("src/dream_test/gherkin/step_trie.gleam", 592).
-spec process_char(binary(), binary(), list(binary()), binary(), boolean()) -> list(binary()).
process_char(Char, Rest, Tokens, Current, In_quotes) ->
case {Char, In_quotes} of
{<<"\""/utf8>>, false} ->
tokenize_preserving_quotes(
Rest,
Tokens,
<<Current/binary, Char/binary>>,
true
);
{<<"\""/utf8>>, true} ->
tokenize_preserving_quotes(
Rest,
Tokens,
<<Current/binary, Char/binary>>,
false
);
{<<" "/utf8>>, false} ->
Updated_tokens = add_token_if_non_empty(Tokens, Current),
tokenize_preserving_quotes(Rest, Updated_tokens, <<""/utf8>>, false);
{_, _} ->
tokenize_preserving_quotes(
Rest,
Tokens,
<<Current/binary, Char/binary>>,
In_quotes
)
end.
-file("src/dream_test/gherkin/step_trie.gleam", 580).
-spec tokenize_preserving_quotes(binary(), list(binary()), binary(), boolean()) -> list(binary()).
tokenize_preserving_quotes(Remaining, Tokens, Current, In_quotes) ->
case gleam_stdlib:string_pop_grapheme(Remaining) of
{error, _} ->
finalize_tokens(Tokens, Current);
{ok, {Char, Rest}} ->
process_char(Char, Rest, Tokens, Current, In_quotes)
end.
-file("src/dream_test/gherkin/step_trie.gleam", 574).
?DOC(
" Tokenize step text for matching.\n"
"\n"
" Prepares step text for trie lookup by splitting it into tokens that align\n"
" with how patterns are parsed. This enables matching patterns with prefixed\n"
" or suffixed placeholders.\n"
"\n"
" ## Tokenization Rules\n"
"\n"
" 1. **Whitespace splitting**: Text is split on spaces\n"
" 2. **Quote preservation**: Quoted strings like `\"Red Widget\"` stay as one token\n"
" 3. **Numeric boundary splitting**: Tokens are split at boundaries between\n"
" numeric and non-numeric characters\n"
"\n"
" The numeric boundary splitting is key for prefix/suffix support. When a\n"
" pattern like `${float}` is parsed, it becomes `[\"$\", \"{float}\"]`. For\n"
" matching to work, the text `$19.99` must also become `[\"$\", \"19.99\"]`.\n"
"\n"
" ## Example\n"
"\n"
" ```gleam\n"
" step_trie.tokenize_step_text(\"I add \\\"Red Widget\\\" and pay $19.99USD\")\n"
" |> should\n"
" |> be_equal([\n"
" \"I\",\n"
" \"add\",\n"
" \"\\\"Red Widget\\\"\",\n"
" \"and\",\n"
" \"pay\",\n"
" \"$\",\n"
" \"19.99\",\n"
" \"USD\",\n"
" ])\n"
" |> or_fail_with(\n"
" \"expected tokenization to preserve quotes and split $19.99USD\",\n"
" )\n"
" ```\n"
).
-spec tokenize_step_text(binary()) -> list(binary()).
tokenize_step_text(Text) ->
_pipe = Text,
_pipe@1 = tokenize_preserving_quotes(_pipe, [], <<""/utf8>>, false),
gleam@list:flat_map(_pipe@1, fun split_numeric_boundaries/1).
-file("src/dream_test/gherkin/step_trie.gleam", 528).
?DOC(
" Look up a step in the trie.\n"
"\n"
" Searches for a handler matching the given keyword and step text.\n"
" Returns the matched handler and captured values, or None if no match.\n"
"\n"
" Lookup is O(word count) - independent of total step definitions.\n"
"\n"
" ## Parameters\n"
"\n"
" - `trie`: The trie to search\n"
" - `keyword`: Step keyword as string (\"Given\", \"When\", \"Then\")\n"
" - `text`: Step text to match (e.g., \"I have 42 items\")\n"
"\n"
" ## Returns\n"
"\n"
" - `Some(StepMatch)`: Contains matched handler and captured values\n"
" - `None`: No step definition matched\n"
"\n"
" ## Example\n"
"\n"
" ```gleam\n"
" step_trie.lookup(trie, \"Then\", \"the total is $19.99USD\")\n"
" |> should\n"
" |> be_equal(\n"
" Some(\n"
" step_trie.StepMatch(handler: \"total_usd\", captures: [\n"
" step_trie.CapturedFloat(19.99),\n"
" ]),\n"
" ),\n"
" )\n"
" |> or_fail_with(\"expected float capture for $19.99USD\")\n"
" ```\n"
).
-spec lookup(step_trie(FUD), binary(), binary()) -> gleam@option:option(step_match(FUD)).
lookup(Trie, Keyword, Text) ->
Words = tokenize_step_text(Text),
lookup_in_node(erlang:element(2, Trie), Keyword, Words, []).