gherkin

    A Gherkin parser for MoonBit with DOM, visitor, fold, and SAX-style APIs

    gherkin
    bdd
    cucumber
    parser
    testing
    Download zip
    Author
    Version
    0.4.0
    License
    Apache-2.0
    Last updated
    2 hours ago
    Downloads
    1K

    Dependencies

    #moonrockz/gherkin

    A Gherkin parser for MoonBit. Parses .feature files used in Behavior-Driven Development (BDD) with Cucumber and similar frameworks.

    #Installation

    moon add moonrockz/gherkin

    #Quick Start

    let source = @gherkin.Source::from_string(
    "Feature: Login\n Scenario: Success\n Given a user\n When they log in\n Then they see the dashboard",
    )
    let doc = @gherkin.parse!(source)
    let feature = doc.feature.unwrap()
    // feature.name == "Login"

    #Four Parsing APIs

    #DOM-Based

    Parse to a full AST for random access to the document tree.

    let doc = @gherkin.parse!(source)
    // doc.feature, doc.comments — full tree

    #Visitor Pattern

    Traverse the AST depth-first, overriding only the node types you care about.

    doc.accept(my_visitor)

    #Functional Fold

    Thread an accumulator through the tree with flow control (Continue, SkipChildren, Stop).

    let count = doc.fold(0, {
    ..@gherkin.GherkinFold::default(),
    visit_step: @gherkin.continuing(fn(n, _) { n + 1 }),
    })

    #SAX-Style Handler

    Push-based event-driven parsing without building an AST.

    @gherkin.parse_with_handler!(source, my_handler)

    #WASM Component

    The parser is available as a WASM Component for use from any language with a Component Model runtime.

    mise run build:component # produces _build/gherkin.component.wasm

    Exports three interfaces (parse, tokenize, write) — see examples/ for Python and JavaScript usage.

    #CLI

    Parse a .feature file to JSON:

    moon run src/cmd/main -- path/to/file.feature

    Parse from stdin:

    echo "Feature: Test" | moon run src/cmd/main -- -

    #License

    Apache-2.0

    #moonrockz/gherkin

    A Gherkin parser providing four APIs: DOM tree, visitor, functional fold, and SAX-style handler.

    #Source

    All parsing starts with a Source, an opaque wrapper around the input text.

    ///|
    test "create a source from a string" {
    let src = @gherkin.Source::from_string(
    "Feature: Hello\n Scenario: World",
    uri="test.feature",
    )
    @debug.debug_inspect(src.uri(), content="Some(\"test.feature\")")
    @debug.debug_inspect(src.line_count(), content="2")
    @debug.debug_inspect(src.line(1), content="Some(\"Feature: Hello\")")
    }

    #DOM Parsing

    parse returns a GherkinDocument containing the full AST.

    ///|
    test "parse a feature with scenarios and steps" {
    let input =
    #|Feature: Calculator
    #| Scenario: Addition
    #| Given two numbers
    #| When I add them
    #| Then I get the sum
    let doc = @gherkin.parse(@gherkin.Source::from_string(input))
    let feature = doc.feature.unwrap()
    @debug.debug_inspect(
    feature.name,
    content=(
    #|"Calculator"
    ),
    )
    @debug.debug_inspect(
    feature.language,
    content=(
    #|"en"
    ),
    )
    feature.children[0] is Scenario(s)
    @debug.debug_inspect(
    s.name,
    content=(
    #|"Addition"
    ),
    )
    @debug.debug_inspect(s.steps.length(), content="3")
    @debug.debug_inspect(s.steps[0].keyword_type, content="Context")
    @debug.debug_inspect(s.steps[1].keyword_type, content="Action")
    @debug.debug_inspect(s.steps[2].keyword_type, content="Outcome")
    }

    #Data Tables

    Steps can have a DataTable argument with rows and cells.

    ///|
    test "parse a step with a data table" {
    let input =
    #|Feature: Users
    #| Scenario: List users
    #| Given users:
    #| | name | age |
    #| | Alice | 30 |
    #| | Bob | 25 |
    let doc = @gherkin.parse(@gherkin.Source::from_string(input))
    doc.feature.unwrap().children[0] is Scenario(s)
    s.steps[0].argument is Some(DataTable(table))
    @debug.debug_inspect(table.rows.length(), content="3")
    @debug.debug_inspect(
    table.rows[0].cells[0].value,
    content=(
    #|"name"
    ),
    )
    @debug.debug_inspect(
    table.rows[1].cells[0].value,
    content=(
    #|"Alice"
    ),
    )
    @debug.debug_inspect(
    table.rows[2].cells[1].value,
    content=(
    #|"25"
    ),
    )
    }

    #Doc Strings

    Steps can have a DocString argument with an optional media type.

    ///|
    test "parse a step with a doc string" {
    let input =
    #|Feature: Payloads
    #| Scenario: JSON body
    #| Given a request body:
    #| ```json
    #| {"key": "value"}
    #| ```
    let doc = @gherkin.parse(@gherkin.Source::from_string(input))
    doc.feature.unwrap().children[0] is Scenario(s)
    s.steps[0].argument is Some(DocString(ds))
    @debug.debug_inspect(ds.media_type, content="Some(\"json\")")
    assert_true(ds.content.contains("key"))
    }

    #Tags

    Tags decorate features, scenarios, and examples.

    ///|
    test "parse tags on features and scenarios" {
    let input =
    #|@smoke @regression
    #|Feature: Tagged
    #| @critical
    #| Scenario: Important
    #| Given a step
    let doc = @gherkin.parse(@gherkin.Source::from_string(input))
    let feature = doc.feature.unwrap()
    @debug.debug_inspect(feature.tags.length(), content="2")
    @debug.debug_inspect(
    feature.tags[0].name,
    content=(
    #|"@smoke"
    ),
    )
    feature.children[0] is Scenario(s)
    @debug.debug_inspect(
    s.tags[0].name,
    content=(
    #|"@critical"
    ),
    )
    }

    #JSON Serialization

    All AST types implement ToJson.

    ///|
    test "serialize a document to JSON" {
    let doc = @gherkin.parse(
    @gherkin.Source::from_string(
    "Feature: JSON\n Scenario: Test\n Given a step",
    ),
    )
    let text = doc.to_json().stringify()
    assert_true(text.contains("JSON"))
    assert_true(text.contains("Test"))
    }

    #Visitor Pattern

    Implement GherkinVisitor and override only the methods you need. All methods have default no-op implementations.

    ///|
    struct ScenarioCounter {
    mut count : Int
    }

    ///|
    impl GherkinVisitor for ScenarioCounter with fn visit_scenario(self, _scenario) {
    self.count 1
    }

    ///|
    test "count scenarios with a visitor" {
    let input =
    #|Feature: Counter
    #| Scenario: First
    #| Given a
    #| Scenario: Second
    #| Given b
    let doc = @gherkin.parse(@gherkin.Source::from_string(input))
    let counter : ScenarioCounter = { count: 0, }
    doc.accept(counter)
    @debug.debug_inspect(counter.count, content="2")
    }

    #Functional Fold

    GherkinFold threads an accumulator through the AST. Use GherkinFold::default() for a passthrough fold, then override fields. The continuing helper lifts plain functions into fold callbacks.

    ///|
    test "count steps with fold" {
    let input =
    #|Feature: Fold
    #| Scenario: One
    #| Given step 1
    #| When step 2
    #| Scenario: Two
    #| Then step 3
    let doc = @gherkin.parse(@gherkin.Source::from_string(input))
    let step_count = doc.fold(0, {
    ..@gherkin.GherkinFold::default(),
    visit_step: @gherkin.continuing(fn(n, _step) { n + 1 }),
    })
    @debug.debug_inspect(step_count, content="3")
    }

    #Flow Control

    FoldAction controls traversal: Continue descends into children, SkipChildren skips children but continues siblings, Stop halts immediately.

    ///|
    let step_count = doc.fold(0, {
    ..GherkinFold::default(),
    visit_scenario: fn(n, scenario) {
    if scenario.tags.iter().any(fn(t) { t.name == "@skip" }) {
    SkipChildren(n) // skip steps inside @skip scenarios
    } else {
    Continue(n)
    }
    },
    visit_step: continuing(fn(n, _) { n + 1 }),
    })

    #SAX-Style Handler

    GherkinHandler receives push-based events as the parser encounters elements. No AST is built — useful for streaming or memory-constrained scenarios.

    ///|
    struct EventLog {
    events : Array[String]
    }

    ///|
    impl GherkinHandler for EventLog with fn on_feature(self, event) {
    self.events.push("feature:\{event.name}")
    }

    ///|
    impl GherkinHandler for EventLog with fn on_scenario(self, event) {
    self.events.push("scenario:\{event.name}")
    }

    ///|
    impl GherkinHandler for EventLog with fn on_step(self, event) {
    self.events.push("step:\{event.text}")
    }

    ///|
    test "log events with a handler" {
    let input =
    #|Feature: Events
    #| Scenario: Example
    #| Given a step
    #| When an action
    let logger : EventLog = { events: [], }
    @gherkin.parse_with_handler(@gherkin.Source::from_string(input), logger)
    @debug.debug_inspect(
    logger.events[0],
    content=(
    #|"feature:Events"
    ),
    )
    @debug.debug_inspect(
    logger.events[1],
    content=(
    #|"scenario:Example"
    ),
    )
    @debug.debug_inspect(
    logger.events[2],
    content=(
    #|"step:a step"
    ),
    )
    @debug.debug_inspect(
    logger.events[3],
    content=(
    #|"step:an action"
    ),
    )
    }

    #Lexer

    The lower-level lexer API is useful for syntax highlighting or custom parsing.

    tokenize converts a full source to tokens eagerly. Lexer provides a lazy iterator.

    ///|
    test "tokenize a source" {
    let tokens = @gherkin.tokenize(
    @gherkin.Source::from_string("Feature: Test\n Given a step"),
    )
    tokens[0] is FeatureLine(_, kw, name)
    @debug.debug_inspect(
    kw,
    content=(
    #|"Feature"
    ),
    )
    @debug.debug_inspect(
    name,
    content=(
    #|"Test"
    ),
    )
    tokens[1] is StepLine(_, _, kt, text)
    @debug.debug_inspect(kt, content="Context")
    @debug.debug_inspect(
    text,
    content=(
    #|"a step"
    ),
    )
    }

    ///|
    test "lazy iteration with Lexer" {
    let lexer = @gherkin.Lexer::new(
    @gherkin.Source::from_string("Given a\nWhen b\nThen c"),
    )
    let mut count = 0
    for tok in lexer.iter() {
    match tok {
    StepLine(_, _, _, _) => count 1
    _ => ()
    }
    }
    @debug.debug_inspect(count, content="3")
    }

    #Error Handling

    Parse errors carry a message and location.

    ///|
    test "handle parse errors" {
    let input =
    #|Feature: Tables
    #| Scenario: Bad
    #| Given a table:
    #| | a | b |
    #| | 1 | 2 | 3 |
    let result = try {
    let _ = @gherkin.parse(@gherkin.Source::from_string(input))
    "ok"
    } catch {
    InconsistentTableCells(message~, ..) => message
    _ => "other"
    }
    assert_true(result.contains("inconsistent cell count"))
    }

    #i18n

    Use # language: xx on the first line to parse non-English Gherkin.

    ///|
    test "parse French Gherkin" {
    let input =
    #|# language: fr
    #|Fonctionnalité: Connexion
    #| Scénario: Succès
    #| Soit un utilisateur
    let doc = @gherkin.parse(@gherkin.Source::from_string(input))
    let feature = doc.feature.unwrap()
    @debug.debug_inspect(
    feature.language,
    content=(
    #|"fr"
    ),
    )
    @debug.debug_inspect(
    feature.keyword,
    content=(
    #|"Fonctionnalité"
    ),
    )
    }

    GherkinHandler

    pub(open) trait GherkinHandler {
    fn on_document(Self) -> Unit = _
    fn on_end_document(Self) -> Unit = _
    fn on_feature(Self, FeatureEvent) -> Unit = _
    fn on_end_feature(Self) -> Unit = _
    fn on_rule(Self, RuleEvent) -> Unit = _
    fn on_end_rule(Self) -> Unit = _
    fn on_background(Self, BackgroundEvent) -> Unit = _
    fn on_end_background(Self) -> Unit = _
    fn on_scenario(Self, ScenarioEvent) -> Unit = _
    fn on_end_scenario(Self) -> Unit = _
    fn on_step(Self, StepEvent) -> Unit = _
    fn on_examples(Self, ExamplesEvent) -> Unit = _
    fn on_tag(Self, TagEvent) -> Unit = _
    fn on_comment(Self, CommentEvent) -> Unit = _
    fn on_doc_string(Self, DocStringEvent) -> Unit = _
    fn on_data_table(Self, DataTableEvent) -> Unit = _
    }

    A handler for push-based (SAX-style) parsing events.

    All methods have default no-op implementations. Override only the methods for events you want to handle.

    The parser drives the process, calling handler methods as it encounters structural elements.

    GherkinVisitor

    pub(open) trait GherkinVisitor {
    fn visit_document(Self, GherkinDocument) -> Unit = _
    fn visit_feature(Self, Feature) -> Unit = _
    fn visit_rule(Self, Rule) -> Unit = _
    fn visit_background(Self, Background) -> Unit = _
    fn visit_scenario(Self, Scenario) -> Unit = _
    fn visit_step(Self, Step) -> Unit = _
    fn visit_doc_string(Self, DocString) -> Unit = _
    fn visit_data_table(Self, DataTable) -> Unit = _
    fn visit_examples(Self, Examples) -> Unit = _
    fn visit_tag(Self, Tag) -> Unit = _
    fn visit_comment(Self, Comment) -> Unit = _
    fn visit_table_row(Self, TableRow) -> Unit = _
    }

    A visitor for traversing a GherkinDocument AST.

    All methods have default no-op implementations. Override only the methods for node types you want to process.

    The data structure controls traversal order (depth-first, document order). Use GherkinDocument::accept to begin traversal.

    ParseError

    pub suberror ParseError {
    UnexpectedToken(message~ : String, location~ : Location)
    UnexpectedEof(message~ : String, location~ : Location)
    InconsistentTableCells(message~ : String, location~ : Location)
    CompositeError(errors~ : Array[ParseError])
    }

    Errors that can occur during Gherkin parsing.

    Background

    pub(all) struct Background {
    location : Location
    keyword : String
    name : String
    description : String
    id : String
    steps : Array[Step]
    } derive(Eq, ToJson,
    Debug
    ,
    FromJson
    )

    A background section providing shared setup steps.

    Background::accept

    fn Background::accept(self : Background, visitor : &GherkinVisitor) -> Unit

    Background::equal

    fn Background::equal(Background, Background) -> Bool

    Background::not_equal

    fn Background::not_equal(x : Background, y : Background) -> Bool

    Background::to_json

    fn Background::to_json(Background) -> Json

    BackgroundEvent

    pub(all) struct BackgroundEvent {
    location : Location
    keyword : String
    name : String
    description : String
    } derive(Eq,
    Debug
    )

    A background start event. Steps arrive as subsequent events.

    BackgroundEvent::equal

    BackgroundEvent::not_equal

    fn BackgroundEvent::not_equal(x : BackgroundEvent, y : BackgroundEvent) -> Bool

    Comment

    pub(all) struct Comment {
    location : Location
    text : String
    } derive(Eq, ToJson,
    Debug
    ,
    FromJson
    )

    A comment line in the source.

    Comment::accept

    fn Comment::accept(self : Comment, visitor : &GherkinVisitor) -> Unit

    Comment::equal

    fn Comment::equal(Comment, Comment) -> Bool

    Comment::not_equal

    fn Comment::not_equal(x : Comment, y : Comment) -> Bool

    Comment::to_json

    fn Comment::to_json(Comment) -> Json

    Comment::to_repr

    CommentEvent

    pub(all) struct CommentEvent {
    location : Location
    text : String
    } derive(Eq,
    Debug
    )

    A comment event (leaf).

    CommentEvent::equal

    CommentEvent::not_equal

    fn CommentEvent::not_equal(x : CommentEvent, y : CommentEvent) -> Bool

    DataTable

    A data table step argument.

    DataTable::accept

    fn DataTable::accept(self : DataTable, visitor : &GherkinVisitor) -> Unit

    DataTable::equal

    fn DataTable::equal(DataTable, DataTable) -> Bool

    DataTable::not_equal

    fn DataTable::not_equal(x : DataTable, y : DataTable) -> Bool

    DataTable::to_json

    fn DataTable::to_json(DataTable) -> Json

    DataTableEvent

    pub(all) struct DataTableEvent {
    location : Location
    rows : Array[TableRow]
    } derive(Eq,
    Debug
    )

    A data table event (self-contained with all rows).

    DataTableEvent::equal

    DataTableEvent::not_equal

    fn DataTableEvent::not_equal(x : DataTableEvent, y : DataTableEvent) -> Bool

    DocString

    pub(all) struct DocString {
    location : Location
    media_type : String?
    content : String
    delimiter : String
    } derive(Eq, ToJson,
    Debug
    ,
    FromJson
    )

    A doc string step argument.

    DocString::accept

    fn DocString::accept(self : DocString, visitor : &GherkinVisitor) -> Unit

    DocString::equal

    fn DocString::equal(DocString, DocString) -> Bool

    DocString::not_equal

    fn DocString::not_equal(x : DocString, y : DocString) -> Bool

    DocString::to_json

    fn DocString::to_json(DocString) -> Json

    DocStringEvent

    pub(all) struct DocStringEvent {
    location : Location
    media_type : String?
    content : String
    delimiter : String
    } derive(Eq,
    Debug
    )

    A doc string event (leaf).

    DocStringEvent::equal

    DocStringEvent::not_equal

    fn DocStringEvent::not_equal(x : DocStringEvent, y : DocStringEvent) -> Bool

    Examples

    pub(all) struct Examples {
    location : Location
    tags : Array[Tag]
    keyword : String
    name : String
    description : String
    id : String
    table_header : TableRow?
    table_body : Array[TableRow]
    } derive(Eq, ToJson,
    Debug
    ,
    FromJson
    )

    An examples table attached to a scenario outline.

    Examples::accept

    fn Examples::accept(self : Examples, visitor : &GherkinVisitor) -> Unit

    Examples::equal

    fn Examples::equal(Examples, Examples) -> Bool

    Examples::not_equal

    fn Examples::not_equal(x : Examples, y : Examples) -> Bool

    Examples::to_json

    fn Examples::to_json(Examples) -> Json

    Examples::to_repr

    ExamplesEvent

    pub(all) struct ExamplesEvent {
    location : Location
    tags : Array[Tag]
    keyword : String
    name : String
    description : String
    table_header : TableRow?
    table_body : Array[TableRow]
    } derive(Eq,
    Debug
    )

    An examples table event (self-contained with header + body).

    ExamplesEvent::equal

    ExamplesEvent::not_equal

    fn ExamplesEvent::not_equal(x : ExamplesEvent, y : ExamplesEvent) -> Bool

    Feature

    pub(all) struct Feature {
    location : Location
    tags : Array[Tag]
    language : String
    keyword : String
    name : String
    description : String
    children : Array[FeatureChild]
    } derive(Eq, ToJson,
    Debug
    ,
    FromJson
    )

    A feature, the top-level structural element in a Gherkin document.

    Feature::accept

    fn Feature::accept(self : Feature, visitor : &GherkinVisitor) -> Unit

    Feature::equal

    fn Feature::equal(Feature, Feature) -> Bool

    Feature::not_equal

    fn Feature::not_equal(x : Feature, y : Feature) -> Bool

    Feature::to_json

    fn Feature::to_json(Feature) -> Json

    Feature::to_repr

    FeatureChild

    pub(all) enum FeatureChild {
    Background(Background)
    Scenario(Scenario)
    Rule(Rule)
    } derive(Eq, ToJson,
    Debug
    ,
    FromJson
    )

    A child element of a Feature: Background, Scenario, or Rule.

    FeatureChild::equal

    FeatureChild::not_equal

    fn FeatureChild::not_equal(x : FeatureChild, y : FeatureChild) -> Bool

    FeatureChild::to_json

    FeatureEvent

    pub(all) struct FeatureEvent {
    location : Location
    tags : Array[Tag]
    language : String
    keyword : String
    name : String
    description : String
    } derive(Eq,
    Debug
    )

    A feature start event. Children arrive as subsequent events.

    FeatureEvent::equal

    FeatureEvent::not_equal

    fn FeatureEvent::not_equal(x : FeatureEvent, y : FeatureEvent) -> Bool

    FoldAction

    pub enum FoldAction[A] {
    Continue(A)
    SkipChildren(A)
    Stop(A)
    } derive(Eq,
    Debug
    )

    Control flow for fold traversal.

    • Continue(a) — proceed into children with updated state
    • SkipChildren(a) — skip children, continue with siblings
    • Stop(a) — halt traversal immediately, return final state

    FoldAction::equal

    fn[A : Eq] FoldAction::equal(FoldAction[A], FoldAction[A]) -> Bool

    FoldAction::is_stop

    fn[A] FoldAction::is_stop(self : FoldAction[A]) -> Bool

    Returns true if this action is Stop.

    FoldAction::not_equal

    fn[A : Eq] FoldAction::not_equal(x : FoldAction[A], y : FoldAction[A]) -> Bool

    FoldAction::value

    fn[A] FoldAction::value(self : FoldAction[A]) -> A

    Extract the accumulated value from a FoldAction.

    GherkinDocument

    pub(all) struct GherkinDocument {
    source : Source
    feature : Feature?
    comments : Array[Comment]
    } derive(Eq, ToJson,
    Debug
    ,
    FromJson
    )

    The root of a parsed Gherkin document.

    GherkinDocument::accept

    fn GherkinDocument::accept(self : GherkinDocument, visitor : &GherkinVisitor) -> Unit

    Accept a visitor on a GherkinDocument, traversing the entire tree in depth-first document order.

    GherkinDocument::empty

    Create an empty GherkinDocument with no feature and no comments.

    GherkinDocument::equal

    GherkinDocument::fold

    fn[A] GherkinDocument::fold(self : GherkinDocument, init : A, folder : GherkinFold[A]) -> A

    Fold over a GherkinDocument AST, threading an accumulator through each node in depth-first document order.

    Respects FoldAction flow control:
    • Continue descends into children
    • SkipChildren skips children, continues with siblings
    • Stop halts traversal and returns immediately

    GherkinDocument::not_equal

    fn GherkinDocument::not_equal(x : GherkinDocument, y : GherkinDocument) -> Bool

    GherkinDocument::to_json

    GherkinEvent

    pub(all) enum GherkinEvent {
    DocumentStart
    DocumentEnd
    FeatureStart(FeatureEvent)
    FeatureEnd
    RuleStart(RuleEvent)
    RuleEnd
    BackgroundStart(BackgroundEvent)
    BackgroundEnd
    ScenarioStart(ScenarioEvent)
    ScenarioEnd
    Step(StepEvent)
    DocString(DocStringEvent)
    DataTable(DataTableEvent)
    Examples(ExamplesEvent)
    Tag(TagEvent)
    Comment(CommentEvent)
    } derive(Eq,
    Debug
    )

    Events emitted during pull-based parsing. Follows nested lifecycle: Document -> Feature -> (Background|Scenario|Rule) -> Steps -> End*

    GherkinEvent::equal

    GherkinEvent::not_equal

    fn GherkinEvent::not_equal(x : GherkinEvent, y : GherkinEvent) -> Bool

    GherkinFold

    pub(all) struct GherkinFold[A] {
    visit_document : (A, GherkinDocument) -> FoldAction[A]
    visit_feature : (A, Feature) -> FoldAction[A]
    visit_rule : (A, Rule) -> FoldAction[A]
    visit_background : (A, Background) -> FoldAction[A]
    visit_scenario : (A, Scenario) -> FoldAction[A]
    visit_step : (A, Step) -> FoldAction[A]
    visit_doc_string : (A, DocString) -> FoldAction[A]
    visit_data_table : (A, DataTable) -> FoldAction[A]
    visit_examples : (A, Examples) -> FoldAction[A]
    visit_tag : (A, Tag) -> FoldAction[A]
    visit_comment : (A, Comment) -> FoldAction[A]
    visit_table_row : (A, TableRow) -> FoldAction[A]
    }

    A fold visitor record for functional accumulation over a Gherkin AST.

    Each field is a callback (A, Node) -> FoldAction[A] that receives the current accumulator and a node, and returns the next accumulator wrapped in a flow-control directive.

    Use GherkinFold::default() to get a fold where every callback passes state through unchanged, then override the ones you need via struct update syntax:

    let count = doc.fold(0, { ..GherkinFold::default(), visit_scenario: fn(n, _) { Continue(n + 1) } })

    GherkinFold::default

    fn[A] GherkinFold::default() -> GherkinFold[A]

    Create a GherkinFold where every callback passes state through unchanged.

    GherkinReader

    pub struct GherkinReader {
    // private fields
    }

    A pull-based event reader for Gherkin documents.

    Produces a stream of GherkinEvent values that the consumer pulls one at a time. Supports peek(), next(), and iter().

    Two construction paths:
    • GherkinReader::new(source) — parses from raw source text
    • GherkinReader::from_document(doc) — walks an existing AST

    Events can be piped to a GherkinHandler via pipe().

    GherkinReader::from_document

    fn GherkinReader::from_document(doc : GherkinDocument) -> GherkinReader

    Create a GherkinReader from an already-parsed document.

    GherkinReader::iter

    Return an Iter[GherkinEvent] that lazily pulls events.

    GherkinReader::new

    fn GherkinReader::new(source : Source) -> GherkinReader raise ParseError

    Create a GherkinReader that parses the given source into events.

    GherkinReader::next

    Return the next event, advancing the reader position. Returns None when all events have been consumed.

    GherkinReader::peek

    Return the next event without advancing the reader position. Returns None when all events have been consumed.

    GherkinReader::pipe

    fn GherkinReader::pipe(self : GherkinReader, handler : &GherkinHandler) -> Unit

    Pipe all remaining events to a GherkinHandler.

    This is the pull-to-push adapter: pulls events from the reader and dispatches them to the handler's callback methods.

    GherkinWriter

    pub struct GherkinWriter {
    // private fields
    }

    A push-based Gherkin writer for building Gherkin text from arbitrary sources.

    Manages indentation and formatting automatically. Call structural methods (feature, scenario, etc.) with matching end_* methods, and leaf methods (step, tags, etc.) for content.

    Implements GherkinHandler, enabling reader-to-writer piping:

    let w = GherkinWriter::new() parse_with_handler(source, w) let output = w.to_string()

    GherkinWriter::background

    fn GherkinWriter::background(self : GherkinWriter, keyword : String, name? : String, description? : String) -> Unit

    Emit a background header. Call end_background() when done.

    GherkinWriter::comment

    fn GherkinWriter::comment(self : GherkinWriter, text : String) -> Unit

    Emit a comment line.

    GherkinWriter::data_table

    fn GherkinWriter::data_table(self : GherkinWriter, rows : Array[Array[String]]) -> Unit

    Emit a data table with column-aligned cells after a step.

    Each inner array is one row of cell values. All rows must have the same number of elements.

    GherkinWriter::doc_string

    fn GherkinWriter::doc_string(self : GherkinWriter, content : String, delimiter? : String, media_type? : String?) -> Unit

    Emit a doc string block after a step.

    GherkinWriter::end_background

    fn GherkinWriter::end_background(self : GherkinWriter) -> Unit

    Close the current background scope.

    GherkinWriter::end_feature

    fn GherkinWriter::end_feature(self : GherkinWriter) -> Unit

    Close the current feature scope.

    GherkinWriter::end_rule

    fn GherkinWriter::end_rule(self : GherkinWriter) -> Unit

    Close the current rule scope.

    GherkinWriter::end_scenario

    fn GherkinWriter::end_scenario(self : GherkinWriter) -> Unit

    Close the current scenario scope.

    GherkinWriter::examples

    fn GherkinWriter::examples(self : GherkinWriter, keyword : String, name? : String, description? : String, header? : Array[String], body? : Array[Array[String]]) -> Unit

    Emit an examples section with a pretty-printed table.

    header is the column names, body is the data rows. Both are arrays of cell value strings.

    GherkinWriter::feature

    fn GherkinWriter::feature(self : GherkinWriter, keyword : String, name : String, language? : String, description? : String) -> Unit

    Emit a feature header. Call end_feature() when done.

    When language is not "en", a # language: directive is emitted first.

    GherkinWriter::new

    Create a new GherkinWriter with the given configuration.

    GherkinWriter::on_background

    fn GherkinWriter::on_background(self : GherkinWriter, e : BackgroundEvent) -> Unit

    GherkinWriter::on_comment

    fn GherkinWriter::on_comment(self : GherkinWriter, e : CommentEvent) -> Unit

    GherkinWriter::on_data_table

    fn GherkinWriter::on_data_table(self : GherkinWriter, e : DataTableEvent) -> Unit

    GherkinWriter::on_doc_string

    fn GherkinWriter::on_doc_string(self : GherkinWriter, e : DocStringEvent) -> Unit

    GherkinWriter::on_document

    fn GherkinWriter::on_document(_self : GherkinWriter) -> Unit

    GherkinWriter::on_end_background

    fn GherkinWriter::on_end_background(self : GherkinWriter) -> Unit

    GherkinWriter::on_end_document

    fn GherkinWriter::on_end_document(_self : GherkinWriter) -> Unit

    GherkinWriter::on_end_feature

    fn GherkinWriter::on_end_feature(self : GherkinWriter) -> Unit

    GherkinWriter::on_end_rule

    fn GherkinWriter::on_end_rule(self : GherkinWriter) -> Unit

    GherkinWriter::on_end_scenario

    fn GherkinWriter::on_end_scenario(self : GherkinWriter) -> Unit

    GherkinWriter::on_examples

    fn GherkinWriter::on_examples(self : GherkinWriter, e : ExamplesEvent) -> Unit

    GherkinWriter::on_feature

    fn GherkinWriter::on_feature(self : GherkinWriter, e : FeatureEvent) -> Unit

    GherkinWriter::on_rule

    fn GherkinWriter::on_rule(self : GherkinWriter, e : RuleEvent) -> Unit

    GherkinWriter::on_scenario

    fn GherkinWriter::on_scenario(self : GherkinWriter, e : ScenarioEvent) -> Unit

    GherkinWriter::on_step

    fn GherkinWriter::on_step(self : GherkinWriter, e : StepEvent) -> Unit

    GherkinWriter::on_tag

    fn GherkinWriter::on_tag(_self : GherkinWriter, _e : TagEvent) -> Unit

    GherkinWriter::rule

    fn GherkinWriter::rule(self : GherkinWriter, keyword : String, name : String, description? : String) -> Unit

    Emit a rule header. Call end_rule() when done.

    GherkinWriter::scenario

    fn GherkinWriter::scenario(self : GherkinWriter, keyword : String, name : String, description? : String) -> Unit

    Emit a scenario header. Call end_scenario() when done.

    GherkinWriter::step

    fn GherkinWriter::step(self : GherkinWriter, keyword : String, text : String) -> Unit

    Emit a step line.

    GherkinWriter::tags

    fn GherkinWriter::tags(self : GherkinWriter, names : Array[String]) -> Unit

    Emit tags on one line, space-separated.

    Call before the structural element the tags decorate. Tags are emitted at the current indent level.

    GherkinWriter::to_string

    fn GherkinWriter::to_string(self : GherkinWriter) -> String

    Return the accumulated Gherkin text.

    KeywordType

    pub(all) enum KeywordType {
    Context
    Action
    Outcome
    Conjunction
    Unknown
    } derive(Eq, ToJson,
    Debug
    ,
    FromJson
    )

    The type of a step keyword, classifying its semantic role.

    KeywordType::equal

    fn KeywordType::equal(KeywordType, KeywordType) -> Bool

    KeywordType::not_equal

    fn KeywordType::not_equal(x : KeywordType, y : KeywordType) -> Bool

    KeywordType::to_json

    fn KeywordType::to_json(KeywordType) -> Json

    Lexer

    pub struct Lexer {
    // private fields
    } derive(
    Debug
    )

    Lexer::iter

    fn Lexer::iter(self : Lexer) -> Iter[Token]

    Return an Iter[Token] that lazily produces tokens from the source.

    Lexer::new

    fn Lexer::new(source : Source) -> Lexer

    Lexer::next

    fn Lexer::next(self : Lexer) -> Token?

    Return the next token, advancing the lexer state.

    Lexer::to_repr

    LexerState

    pub enum LexerState {
    Normal
    InDocString(String)
    } derive(Eq, ToJson,
    Debug
    )

    Internal state tracked between lines during tokenization. Gherkin is mostly stateless line-by-line except inside doc strings.

    LexerState::equal

    fn LexerState::equal(LexerState, LexerState) -> Bool

    LexerState::not_equal

    fn LexerState::not_equal(x : LexerState, y : LexerState) -> Bool

    LexerState::to_json

    fn LexerState::to_json(LexerState) -> Json

    Location

    pub(all) struct Location {
    line : Int
    column : Int?
    } derive(Eq, ToJson,
    Debug
    ,
    FromJson
    )

    A location in the source text.

    Location::equal

    fn Location::equal(Location, Location) -> Bool

    Location::not_equal

    fn Location::not_equal(x : Location, y : Location) -> Bool

    Location::to_json

    fn Location::to_json(Location) -> Json

    Location::to_repr

    Rule

    pub(all) struct Rule {
    location : Location
    tags : Array[Tag]
    keyword : String
    name : String
    description : String
    id : String
    children : Array[RuleChild]
    } derive(Eq, ToJson,
    Debug
    ,
    FromJson
    )

    A rule grouping related scenarios under a business rule.

    Rule::accept

    fn Rule::accept(self : Rule, visitor : &GherkinVisitor) -> Unit

    Rule::equal

    fn Rule::equal(Rule, Rule) -> Bool

    Rule::not_equal

    fn Rule::not_equal(x : Rule, y : Rule) -> Bool

    Rule::to_json

    fn Rule::to_json(Rule) -> Json

    Rule::to_repr

    RuleChild

    A child element of a Rule: either a Background or a Scenario.

    RuleChild::equal

    fn RuleChild::equal(RuleChild, RuleChild) -> Bool

    RuleChild::not_equal

    fn RuleChild::not_equal(x : RuleChild, y : RuleChild) -> Bool

    RuleChild::to_json

    fn RuleChild::to_json(RuleChild) -> Json

    RuleEvent

    pub(all) struct RuleEvent {
    location : Location
    tags : Array[Tag]
    keyword : String
    name : String
    description : String
    } derive(Eq,
    Debug
    )

    A rule start event. Children arrive as subsequent events.

    RuleEvent::equal

    fn RuleEvent::equal(RuleEvent, RuleEvent) -> Bool

    RuleEvent::not_equal

    fn RuleEvent::not_equal(x : RuleEvent, y : RuleEvent) -> Bool

    Scenario

    pub(all) struct Scenario {
    location : Location
    tags : Array[Tag]
    kind : ScenarioKind
    keyword : String
    name : String
    description : String
    id : String
    steps : Array[Step]
    examples : Array[Examples]
    } derive(Eq, ToJson,
    Debug
    ,
    FromJson
    )

    A scenario (or scenario outline) within a feature or rule.

    Scenario::accept

    fn Scenario::accept(self : Scenario, visitor : &GherkinVisitor) -> Unit

    Scenario::equal

    fn Scenario::equal(Scenario, Scenario) -> Bool

    Scenario::not_equal

    fn Scenario::not_equal(x : Scenario, y : Scenario) -> Bool

    Scenario::to_json

    fn Scenario::to_json(Scenario) -> Json

    Scenario::to_repr

    ScenarioEvent

    pub(all) struct ScenarioEvent {
    location : Location
    tags : Array[Tag]
    kind : ScenarioKind
    keyword : String
    name : String
    description : String
    } derive(Eq,
    Debug
    )

    A scenario start event. Steps/examples arrive as subsequent events.

    ScenarioEvent::equal

    ScenarioEvent::not_equal

    fn ScenarioEvent::not_equal(x : ScenarioEvent, y : ScenarioEvent) -> Bool

    ScenarioKind

    pub(all) enum ScenarioKind {
    Scenario
    ScenarioOutline
    } derive(Eq, ToJson,
    Debug
    ,
    FromJson
    )

    Distinguishes a Scenario from a Scenario Outline.

    ScenarioKind::equal

    ScenarioKind::not_equal

    fn ScenarioKind::not_equal(x : ScenarioKind, y : ScenarioKind) -> Bool

    ScenarioKind::to_json

    Source

    pub struct Source {
    // private fields
    } derive(Eq,
    Debug
    )

    A source of Gherkin text, bundling content with metadata.

    The internal representation is opaque — callers use factory functions to create and accessor methods to read. This allows the internal representation to evolve without breaking consumers.
    impl ToJson for Source
    impl FromJson for Source

    Source::content

    fn Source::content(self : Source) -> String

    Returns the full content as a single string, with lines joined by \n.

    This allocates a new string. For line-by-line access, prefer line().

    Source::equal

    fn Source::equal(Source, Source) -> Bool

    Source::from_bytes

    fn Source::from_bytes(bytes : Bytes, uri? : String) -> Source

    Create a Source from UTF-8 encoded bytes.

    Decodes the bytes to a string, then splits into lines. Invalid UTF-8 sequences are replaced with the Unicode replacement character.

    Source::from_string

    fn Source::from_string(content : String, uri? : String) -> Source

    Create a Source from a string, splitting into lines.

    Handles both Unix (\n) and Windows (\r\n) line endings. The optional uri identifies where the content came from (file path, URL, etc.).

    Source::line

    fn Source::line(self : Source, n : Int) -> String?

    Returns the line at the given 1-based line number, matching Location.line.

    Returns None if the line number is out of range.

    Source::line_count

    fn Source::line_count(self : Source) -> Int

    Returns the number of lines in this source.

    Source::not_equal

    fn Source::not_equal(x : Source, y : Source) -> Bool

    Source::to_json

    fn Source::to_json(self : Source) -> Json

    Source::to_repr

    Source::uri

    fn Source::uri(self : Source) -> String?

    Returns the URI identifying this source, if any.

    Step

    pub(all) struct Step {
    location : Location
    keyword : String
    keyword_type : KeywordType
    text : String
    id : String
    argument : StepArgument?
    } derive(Eq, ToJson,
    Debug
    ,
    FromJson
    )

    A single step within a scenario or background.

    Step::accept

    fn Step::accept(self : Step, visitor : &GherkinVisitor) -> Unit

    Step::equal

    fn Step::equal(Step, Step) -> Bool

    Step::not_equal

    fn Step::not_equal(x : Step, y : Step) -> Bool

    Step::to_json

    fn Step::to_json(Step) -> Json

    Step::to_repr

    StepArgument

    pub(all) enum StepArgument {
    DocString(DocString)
    DataTable(DataTable)
    } derive(Eq, ToJson,
    Debug
    ,
    FromJson
    )

    A step argument is either a DocString or a DataTable.

    StepArgument::equal

    StepArgument::not_equal

    fn StepArgument::not_equal(x : StepArgument, y : StepArgument) -> Bool

    StepArgument::to_json

    StepEvent

    pub(all) struct StepEvent {
    location : Location
    keyword : String
    keyword_type : KeywordType
    text : String
    } derive(Eq,
    Debug
    )

    A step event (leaf -- no children).

    StepEvent::equal

    fn StepEvent::equal(StepEvent, StepEvent) -> Bool

    StepEvent::not_equal

    fn StepEvent::not_equal(x : StepEvent, y : StepEvent) -> Bool

    TableCell

    pub(all) struct TableCell {
    location : Location
    value : String
    } derive(Eq, ToJson,
    Debug
    ,
    FromJson
    )

    A single cell in a data table row.

    TableCell::equal

    fn TableCell::equal(TableCell, TableCell) -> Bool

    TableCell::not_equal

    fn TableCell::not_equal(x : TableCell, y : TableCell) -> Bool

    TableCell::to_json

    fn TableCell::to_json(TableCell) -> Json

    TableRow

    pub(all) struct TableRow {
    location : Location
    id : String
    cells : Array[TableCell]
    } derive(Eq, ToJson,
    Debug
    ,
    FromJson
    )

    A row in a data table or examples table.

    TableRow::accept

    fn TableRow::accept(self : TableRow, visitor : &GherkinVisitor) -> Unit

    TableRow::equal

    fn TableRow::equal(TableRow, TableRow) -> Bool

    TableRow::not_equal

    fn TableRow::not_equal(x : TableRow, y : TableRow) -> Bool

    TableRow::to_json

    fn TableRow::to_json(TableRow) -> Json

    TableRow::to_repr

    Tag

    pub(all) struct Tag {
    location : Location
    name : String
    id : String
    } derive(Eq, ToJson,
    Debug
    ,
    FromJson
    )

    A tag as it appears in source. The name field includes the '@' prefix (e.g., "@smoke").

    Tag::accept

    fn Tag::accept(self : Tag, visitor : &GherkinVisitor) -> Unit

    Tag::equal

    fn Tag::equal(Tag, Tag) -> Bool

    Tag::not_equal

    fn Tag::not_equal(x : Tag, y : Tag) -> Bool

    Tag::to_json

    fn Tag::to_json(Tag) -> Json

    Tag::to_repr

    TagEvent

    pub(all) struct TagEvent {
    location : Location
    name : String
    } derive(Eq,
    Debug
    )

    A tag event (leaf).

    TagEvent::equal

    fn TagEvent::equal(TagEvent, TagEvent) -> Bool

    TagEvent::not_equal

    fn TagEvent::not_equal(x : TagEvent, y : TagEvent) -> Bool

    TagEvent::to_repr

    Token

    pub enum Token {
    FeatureLine(Location, String, String)
    RuleLine(Location, String, String)
    BackgroundLine(Location, String, String)
    ScenarioLine(Location, String, String, ScenarioKind)
    ExamplesLine(Location, String, String)
    StepLine(Location, String, KeywordType, String)
    DocStringSeparator(Location, String, String?)
    TableRow(Location, Array[String])
    TagLine(Location, Array[String])
    Comment(Location, String)
    Language(Location, String)
    Empty(Location)
    Other(Location, String)
    Eof(Location)
    } derive(Eq, ToJson,
    Debug
    )

    A token produced by the lexer, representing a classified source line. Each variant carries its Location and the relevant parsed fragments.

    Token::equal

    fn Token::equal(Token, Token) -> Bool

    Token::not_equal

    fn Token::not_equal(x : Token, y : Token) -> Bool

    Token::to_json

    fn Token::to_json(Token) -> Json

    Token::to_repr

    WriteConfig

    pub(all) struct WriteConfig {
    indent : String
    table_cell_padding : Int
    trailing_newline : Bool
    include_comments : Bool
    } derive(Eq,
    Debug
    )

    Configuration for formatting Gherkin output.

    Use WriteConfig::default() for standard formatting, then override specific fields via struct update syntax:

    let config = { ..WriteConfig::default(), indent: " " }

    WriteConfig::default

    fn WriteConfig::default() -> WriteConfig

    Create a WriteConfig with standard Gherkin formatting defaults.

    WriteConfig::equal

    fn WriteConfig::equal(WriteConfig, WriteConfig) -> Bool

    WriteConfig::not_equal

    fn WriteConfig::not_equal(x : WriteConfig, y : WriteConfig) -> Bool

    classify_line

    fn classify_line(line : String, line_num : Int, state : LexerState, language? : String) -> (Token, LexerState)

    Classify a single source line into a Token, returning the updated LexerState.

    This is the core pure function of the lexer. It examines one line at a time, using the current state to handle doc string regions correctly. The language parameter selects the keyword table for matching Gherkin keywords (Feature, Scenario, Given, etc.). Defaults to English ("en").

    continuing

    fn[A, N] continuing(f : (A, N) -> A) -> ((A, N) -> FoldAction[A])

    Lift a plain (A, N) -> A function into a FoldAction-returning callback that always continues into children.

    This reduces boilerplate when you don't need flow control:

    // Without continuing: visit_scenario: fn(n, _) { Continue(n + 1) } // With continuing: visit_scenario: continuing(fn(n, _) { n + 1 })

    parse

    fn parse(source : Source) -> GherkinDocument raise ParseError

    Parse a Gherkin document from a Source.

    parse_with_handler

    fn parse_with_handler(source : Source, handler : &GherkinHandler) -> Unit raise ParseError

    Parse a Source, pushing events to a handler.

    tokenize

    fn tokenize(source : Source) -> Array[Token]

    write

    fn write(doc : GherkinDocument, config? : WriteConfig) -> String

    Write a GherkinDocument AST as formatted Gherkin text.

    Produces well-formatted output with column-aligned tables, properly indented steps, and preserved i18n keywords.

    This is the inverse of parse: calling write(parse(source)) produces valid Gherkin that, when re-parsed, yields an equivalent AST.