-module(dream_test@gherkin@parser). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "src/dream_test/gherkin/parser.gleam"). -export([parse_string/1, parse_file/1]). -export_type([parser_state/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. ?MODULEDOC( " Parse Gherkin `.feature` files into `gherkin/types.Feature`.\n" "\n" " Use this when you have Gherkin text (from a file on disk, a fixture, or a\n" " string literal) and you want a structured representation you can convert\n" " into runnable tests via `dream_test/gherkin/feature`.\n" "\n" " The parser supports common Gherkin syntax:\n" " Feature / Scenario / Scenario Outline, Background, tags (`@tag`), steps,\n" " DocStrings (`\"\"\"`), DataTables (`| ... |`), Examples tables, and comments.\n" ). -type parser_state() :: {parser_state, gleam@option:option(binary()), gleam@option:option(binary()), list(binary()), gleam@option:option(dream_test@gherkin@types:background()), list(dream_test@gherkin@types:scenario()), gleam@option:option(binary()), list(binary()), list(dream_test@gherkin@types:step()), gleam@option:option(dream_test@gherkin@types:examples_table()), list(binary()), boolean(), list(binary()), gleam@option:option(binary()), boolean(), boolean()}. -file("src/dream_test/gherkin/parser.gleam", 56). -spec feature_with_source(dream_test@gherkin@types:feature(), binary()) -> dream_test@gherkin@types:feature(). feature_with_source(Feature, Source) -> {feature, Name, _, Description, Tags, Background, Scenarios} = Feature, {feature, Name, {some, Source}, Description, Tags, Background, Scenarios}. -file("src/dream_test/gherkin/parser.gleam", 136). -spec initial_state() -> parser_state(). initial_state() -> {parser_state, none, none, [], none, [], none, [], [], none, [], false, [], none, false, false}. -file("src/dream_test/gherkin/parser.gleam", 196). -spec close_doc_string(parser_state()) -> {ok, parser_state()} | {error, binary()}. close_doc_string(State) -> Content = gleam@string:join( lists:reverse(erlang:element(13, State)), <<"\n"/utf8>> ), Doc_string = {doc_string, Content, erlang:element(14, State)}, case lists:reverse(erlang:element(9, State)) of [{step, Keyword, Text, _} | Rest] -> Updated_step = {step, Keyword, Text, {some, Doc_string}}, New_steps = lists:reverse([Updated_step | Rest]), {ok, {parser_state, erlang:element(2, State), erlang:element(3, State), erlang:element(4, State), erlang:element(5, State), erlang:element(6, State), erlang:element(7, State), erlang:element(8, State), New_steps, erlang:element(10, State), erlang:element(11, State), false, [], none, erlang:element(15, State), erlang:element(16, State)}}; [] -> {error, <<"DocString without preceding step"/utf8>>} end. -file("src/dream_test/gherkin/parser.gleam", 220). -spec append_doc_string_line(binary(), parser_state()) -> {ok, parser_state()} | {error, binary()}. append_doc_string_line(Line, State) -> {ok, {parser_state, erlang:element(2, State), erlang:element(3, State), erlang:element(4, State), erlang:element(5, State), erlang:element(6, State), erlang:element(7, State), erlang:element(8, State), erlang:element(9, State), erlang:element(10, State), erlang:element(11, State), erlang:element(12, State), [Line | erlang:element(13, State)], erlang:element(14, State), erlang:element(15, State), erlang:element(16, State)}}. -file("src/dream_test/gherkin/parser.gleam", 185). -spec handle_doc_string_line(binary(), binary(), parser_state()) -> {ok, parser_state()} | {error, binary()}. handle_doc_string_line(Trimmed, Original, State) -> case gleam_stdlib:string_starts_with(Trimmed, <<"\"\"\""/utf8>>) of true -> close_doc_string(State); false -> append_doc_string_line(Original, State) end. -file("src/dream_test/gherkin/parser.gleam", 290). -spec is_tag(binary()) -> boolean(). is_tag(Word) -> gleam_stdlib:string_starts_with(Word, <<"@"/utf8>>). -file("src/dream_test/gherkin/parser.gleam", 294). -spec extract_tag_name(binary()) -> binary(). extract_tag_name(Tag) -> gleam@string:drop_start(Tag, 1). -file("src/dream_test/gherkin/parser.gleam", 283). -spec extract_tags(binary()) -> list(binary()). extract_tags(Line) -> _pipe = Line, _pipe@1 = gleam@string:split(_pipe, <<" "/utf8>>), _pipe@2 = gleam@list:filter(_pipe@1, fun is_tag/1), gleam@list:map(_pipe@2, fun extract_tag_name/1). -file("src/dream_test/gherkin/parser.gleam", 278). -spec parse_tags(binary(), parser_state()) -> {ok, parser_state()} | {error, binary()}. parse_tags(Line, State) -> Tags = extract_tags(Line), {ok, {parser_state, erlang:element(2, State), erlang:element(3, State), erlang:element(4, State), erlang:element(5, State), erlang:element(6, State), erlang:element(7, State), erlang:element(8, State), erlang:element(9, State), erlang:element(10, State), lists:append(erlang:element(11, State), Tags), erlang:element(12, State), erlang:element(13, State), erlang:element(14, State), erlang:element(15, State), erlang:element(16, State)}}. -file("src/dream_test/gherkin/parser.gleam", 302). -spec parse_feature_line(binary(), parser_state()) -> {ok, parser_state()} | {error, binary()}. parse_feature_line(Name, State) -> {ok, {parser_state, {some, gleam@string:trim(Name)}, erlang:element(3, State), erlang:element(11, State), erlang:element(5, State), erlang:element(6, State), erlang:element(7, State), erlang:element(8, State), erlang:element(9, State), erlang:element(10, State), [], erlang:element(12, State), erlang:element(13, State), erlang:element(14, State), erlang:element(15, State), erlang:element(16, State)}}. -file("src/dream_test/gherkin/parser.gleam", 320). -spec start_background(parser_state()) -> {ok, parser_state()} | {error, binary()}. start_background(State) -> {ok, {parser_state, erlang:element(2, State), erlang:element(3, State), erlang:element(4, State), erlang:element(5, State), erlang:element(6, State), erlang:element(7, State), erlang:element(8, State), [], erlang:element(10, State), erlang:element(11, State), erlang:element(12, State), erlang:element(13, State), erlang:element(14, State), true, erlang:element(16, State)}}. -file("src/dream_test/gherkin/parser.gleam", 377). -spec finalize_background(parser_state()) -> parser_state(). finalize_background(State) -> case erlang:element(9, State) of [] -> State; Steps -> Background = {background, lists:reverse(Steps)}, {parser_state, erlang:element(2, State), erlang:element(3, State), erlang:element(4, State), {some, Background}, erlang:element(6, State), erlang:element(7, State), erlang:element(8, State), [], erlang:element(10, State), erlang:element(11, State), erlang:element(12, State), erlang:element(13, State), erlang:element(14, State), false, erlang:element(16, State)} end. -file("src/dream_test/gherkin/parser.gleam", 418). -spec build_scenario( binary(), list(binary()), list(dream_test@gherkin@types:step()), gleam@option:option(dream_test@gherkin@types:examples_table()), boolean() ) -> dream_test@gherkin@types:scenario(). build_scenario(Name, Tags, Steps, Examples, Is_outline) -> case {Is_outline, Examples} of {true, {some, Ex}} -> {scenario_outline, Name, Tags, Steps, Ex}; {true, none} -> {scenario_outline, Name, Tags, Steps, dream_test@gherkin@types:empty_examples()}; {false, _} -> {scenario, Name, Tags, Steps} end. -file("src/dream_test/gherkin/parser.gleam", 392). -spec finalize_scenario(parser_state()) -> parser_state(). finalize_scenario(State) -> case erlang:element(7, State) of none -> State; {some, Name} -> Steps = lists:reverse(erlang:element(9, State)), Scenario = build_scenario( Name, erlang:element(8, State), Steps, erlang:element(10, State), erlang:element(16, State) ), {parser_state, erlang:element(2, State), erlang:element(3, State), erlang:element(4, State), erlang:element(5, State), [Scenario | erlang:element(6, State)], none, [], [], none, erlang:element(11, State), erlang:element(12, State), erlang:element(13, State), erlang:element(14, State), erlang:element(15, State), false} end. -file("src/dream_test/gherkin/parser.gleam", 370). -spec finalize_current_scenario(parser_state()) -> parser_state(). finalize_current_scenario(State) -> case erlang:element(15, State) of true -> finalize_background(State); false -> finalize_scenario(State) end. -file("src/dream_test/gherkin/parser.gleam", 328). -spec start_scenario(binary(), parser_state()) -> {ok, parser_state()} | {error, binary()}. start_scenario(Name, State) -> State_with_previous = finalize_current_scenario(State), {ok, {parser_state, erlang:element(2, State_with_previous), erlang:element(3, State_with_previous), erlang:element(4, State_with_previous), erlang:element(5, State_with_previous), erlang:element(6, State_with_previous), {some, gleam@string:trim(Name)}, erlang:element(11, State_with_previous), [], none, [], erlang:element(12, State_with_previous), erlang:element(13, State_with_previous), erlang:element(14, State_with_previous), false, false}}. -file("src/dream_test/gherkin/parser.gleam", 349). -spec start_scenario_outline(binary(), parser_state()) -> {ok, parser_state()} | {error, binary()}. start_scenario_outline(Name, State) -> State_with_previous = finalize_current_scenario(State), {ok, {parser_state, erlang:element(2, State_with_previous), erlang:element(3, State_with_previous), erlang:element(4, State_with_previous), erlang:element(5, State_with_previous), erlang:element(6, State_with_previous), {some, gleam@string:trim(Name)}, erlang:element(11, State_with_previous), [], none, [], erlang:element(12, State_with_previous), erlang:element(13, State_with_previous), erlang:element(14, State_with_previous), false, true}}. -file("src/dream_test/gherkin/parser.gleam", 443). -spec parse_step( dream_test@gherkin@types:step_keyword(), binary(), parser_state() ) -> {ok, parser_state()} | {error, binary()}. parse_step(Keyword, Text, State) -> Step = {step, Keyword, gleam@string:trim(Text), none}, {ok, {parser_state, erlang:element(2, State), erlang:element(3, State), erlang:element(4, State), erlang:element(5, State), erlang:element(6, State), erlang:element(7, State), erlang:element(8, State), [Step | erlang:element(9, State)], erlang:element(10, State), erlang:element(11, State), erlang:element(12, State), erlang:element(13, State), erlang:element(14, State), erlang:element(15, State), erlang:element(16, State)}}. -file("src/dream_test/gherkin/parser.gleam", 456). -spec start_doc_string(binary(), parser_state()) -> {ok, parser_state()} | {error, binary()}. start_doc_string(Type_hint, State) -> Content_type = case gleam@string:trim(Type_hint) of <<""/utf8>> -> none; T -> {some, T} end, {ok, {parser_state, erlang:element(2, State), erlang:element(3, State), erlang:element(4, State), erlang:element(5, State), erlang:element(6, State), erlang:element(7, State), erlang:element(8, State), erlang:element(9, State), erlang:element(10, State), erlang:element(11, State), true, [], Content_type, erlang:element(15, State), erlang:element(16, State)}}. -file("src/dream_test/gherkin/parser.gleam", 498). -spec is_non_empty_cell(binary()) -> boolean(). is_non_empty_cell(Cell) -> gleam@string:trim(Cell) /= <<""/utf8>>. -file("src/dream_test/gherkin/parser.gleam", 491). -spec parse_table_cells(binary()) -> list(binary()). parse_table_cells(Line) -> _pipe = Line, _pipe@1 = gleam@string:split(_pipe, <<"|"/utf8>>), _pipe@2 = gleam@list:filter(_pipe@1, fun is_non_empty_cell/1), gleam@list:map(_pipe@2, fun gleam@string:trim/1). -file("src/dream_test/gherkin/parser.gleam", 502). -spec add_examples_row( list(binary()), dream_test@gherkin@types:examples_table(), parser_state() ) -> {ok, parser_state()} | {error, binary()}. add_examples_row(Cells, Examples, State) -> Updated = case erlang:element(2, Examples) of [] -> {examples_table, Cells, []}; _ -> {examples_table, erlang:element(2, Examples), lists:append(erlang:element(3, Examples), [Cells])} end, {ok, {parser_state, erlang:element(2, State), erlang:element(3, State), erlang:element(4, State), erlang:element(5, State), erlang:element(6, State), erlang:element(7, State), erlang:element(8, State), erlang:element(9, State), {some, Updated}, erlang:element(11, State), erlang:element(12, State), erlang:element(13, State), erlang:element(14, State), erlang:element(15, State), erlang:element(16, State)}}. -file("src/dream_test/gherkin/parser.gleam", 518). -spec add_data_table_row(list(binary()), parser_state()) -> {ok, parser_state()} | {error, binary()}. add_data_table_row(Cells, State) -> case lists:reverse(erlang:element(9, State)) of [{step, Keyword, Text, Arg} | Rest] -> Updated_arg = case Arg of {some, {data_table, Rows}} -> {data_table, lists:append(Rows, [Cells])}; {some, Other} -> Other; none -> {data_table, [Cells]} end, Updated_step = {step, Keyword, Text, {some, Updated_arg}}, New_steps = lists:reverse([Updated_step | Rest]), {ok, {parser_state, erlang:element(2, State), erlang:element(3, State), erlang:element(4, State), erlang:element(5, State), erlang:element(6, State), erlang:element(7, State), erlang:element(8, State), New_steps, erlang:element(10, State), erlang:element(11, State), erlang:element(12, State), erlang:element(13, State), erlang:element(14, State), erlang:element(15, State), erlang:element(16, State)}}; [] -> {error, <<"DataTable without preceding step"/utf8>>} end. -file("src/dream_test/gherkin/parser.gleam", 478). -spec parse_table_row(binary(), parser_state()) -> {ok, parser_state()} | {error, binary()}. parse_table_row(Line, State) -> Cells = parse_table_cells(Line), case erlang:element(10, State) of {some, Ex} -> add_examples_row(Cells, Ex, State); none -> add_data_table_row(Cells, State) end. -file("src/dream_test/gherkin/parser.gleam", 542). -spec start_examples(parser_state()) -> {ok, parser_state()} | {error, binary()}. start_examples(State) -> {ok, {parser_state, erlang:element(2, State), erlang:element(3, State), erlang:element(4, State), erlang:element(5, State), erlang:element(6, State), erlang:element(7, State), erlang:element(8, State), erlang:element(9, State), {some, dream_test@gherkin@types:empty_examples()}, erlang:element(11, State), erlang:element(12, State), erlang:element(13, State), erlang:element(14, State), erlang:element(15, State), erlang:element(16, State)}}. -file("src/dream_test/gherkin/parser.gleam", 550). -spec parse_description_or_unknown(binary(), parser_state()) -> {ok, parser_state()} | {error, binary()}. parse_description_or_unknown(Line, State) -> case {erlang:element(2, State), erlang:element(7, State)} of {{some, _}, none} -> Description = case erlang:element(3, State) of none -> Line; {some, Existing} -> <<<>/binary, Line/binary>> end, {ok, {parser_state, erlang:element(2, State), {some, Description}, erlang:element(4, State), erlang:element(5, State), erlang:element(6, State), erlang:element(7, State), erlang:element(8, State), erlang:element(9, State), erlang:element(10, State), erlang:element(11, State), erlang:element(12, State), erlang:element(13, State), erlang:element(14, State), erlang:element(15, State), erlang:element(16, State)}}; {_, _} -> {ok, State} end. -file("src/dream_test/gherkin/parser.gleam", 229). -spec handle_normal_line(binary(), parser_state()) -> {ok, parser_state()} | {error, binary()}. handle_normal_line(Trimmed, State) -> case Trimmed of <<""/utf8>> -> {ok, State}; <<"#"/utf8, _/binary>> -> {ok, State}; <<"@"/utf8, _/binary>> -> parse_tags(Trimmed, State); <<"Feature:"/utf8, Rest/binary>> -> parse_feature_line(Rest, State); <<"Background:"/utf8>> -> start_background(State); <<"Scenario:"/utf8, Rest@1/binary>> -> start_scenario(Rest@1, State); <<"Scenario Outline:"/utf8, Rest@2/binary>> -> start_scenario_outline(Rest@2, State); <<"Examples:"/utf8>> -> start_examples(State); <<"Given "/utf8, Rest@3/binary>> -> parse_step(given, Rest@3, State); <<"When "/utf8, Rest@4/binary>> -> parse_step('when', Rest@4, State); <<"Then "/utf8, Rest@5/binary>> -> parse_step(then, Rest@5, State); <<"And "/utf8, Rest@6/binary>> -> parse_step('and', Rest@6, State); <<"But "/utf8, Rest@7/binary>> -> parse_step(but, Rest@7, State); <<"\"\"\""/utf8, Type_hint/binary>> -> start_doc_string(Type_hint, State); <<"|"/utf8, _/binary>> -> parse_table_row(Trimmed, State); _ -> parse_description_or_unknown(Trimmed, State) end. -file("src/dream_test/gherkin/parser.gleam", 175). -spec parse_line(binary(), parser_state()) -> {ok, parser_state()} | {error, binary()}. parse_line(Line, State) -> Trimmed = gleam@string:trim(Line), case erlang:element(12, State) of true -> handle_doc_string_line(Trimmed, Line, State); false -> handle_normal_line(Trimmed, State) end. -file("src/dream_test/gherkin/parser.gleam", 572). -spec finalize_feature(parser_state()) -> {ok, dream_test@gherkin@types:feature()} | {error, binary()}. finalize_feature(State) -> Final_state = finalize_current_scenario(State), case erlang:element(2, Final_state) of none -> {error, <<"No Feature found in content"/utf8>>}; {some, Name} -> {ok, {feature, Name, none, erlang:element(3, Final_state), erlang:element(4, Final_state), erlang:element(5, Final_state), lists:reverse(erlang:element(6, Final_state))}} end. -file("src/dream_test/gherkin/parser.gleam", 160). -spec parse_lines(list(binary()), parser_state()) -> {ok, dream_test@gherkin@types:feature()} | {error, binary()}. parse_lines(Lines, State) -> case Lines of [] -> finalize_feature(State); [Line | Rest] -> case parse_line(Line, State) of {ok, New_state} -> parse_lines(Rest, New_state); {error, Msg} -> {error, Msg} end end. -file("src/dream_test/gherkin/parser.gleam", 106). ?DOC( " Parse Gherkin content from a string.\n" "\n" " Parses the provided string as Gherkin syntax.\n" "\n" " ## Parameters\n" "\n" " - `content`: Gherkin content as a string\n" "\n" " ## Returns\n" "\n" " - `Ok(Feature)`: Successfully parsed feature\n" " - `Error(String)`: Parse error with description\n" "\n" " ## Example\n" "\n" " ```gleam\n" " let content =\n" " \"@smoke\\n\"\n" " <> \"Feature: Demo\\n\"\n" " <> \"\\n\"\n" " <> \" Scenario: One\\n\"\n" " <> \" Given a thing\\n\"\n" "\n" " use feature <- result.try(parser.parse_string(content))\n" "\n" " feature.name\n" " |> should\n" " |> be_equal(\"Demo\")\n" " |> or_fail_with(\"expected feature name Demo\")\n" " ```\n" ). -spec parse_string(binary()) -> {ok, dream_test@gherkin@types:feature()} | {error, binary()}. parse_string(Content) -> Lines = gleam@string:split(Content, <<"\n"/utf8>>), State = initial_state(), parse_lines(Lines, State). -file("src/dream_test/gherkin/parser.gleam", 44). ?DOC( " Parse a `.feature` file from disk.\n" "\n" " Reads the file and parses its contents into a Feature.\n" "\n" " ## Parameters\n" "\n" " - `path`: Path to the `.feature` file\n" "\n" " ## Returns\n" "\n" " - `Ok(Feature)`: Successfully parsed feature\n" " - `Error(String)`: Parse error with description\n" "\n" " ## Example\n" "\n" " ```gleam\n" " let assert Ok(feature) = parser.parse_file(\"test/cart.feature\")\n" " ```\n" ). -spec parse_file(binary()) -> {ok, dream_test@gherkin@types:feature()} | {error, binary()}. parse_file(Path) -> case dream_test_file_ffi:read_file(Path) of {ok, Content} -> case parse_string(Content) of {ok, Feature} -> {ok, feature_with_source(Feature, Path)}; {error, Message} -> {error, Message} end; {error, Error} -> {error, <<"Failed to read feature file: "/utf8, (dream_test@file:error_to_string(Error))/binary>>} end.