///|
/// 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()`.
pub struct GherkinReader {
priv events : Array[GherkinEvent]
priv mut pos : Int
}
///|
/// Create a `GherkinReader` that parses the given source into events.
pub fn GherkinReader::new(source : Source) -> GherkinReader raise ParseError {
let doc = parse(source)
GherkinReader::from_document(doc)
}
///|
/// Create a `GherkinReader` from an already-parsed document.
pub fn GherkinReader::from_document(doc : GherkinDocument) -> GherkinReader {
let events : Array[GherkinEvent] = []
events.push(DocumentStart)
for c in doc.comments {
events.push(Comment({ location: c.location, text: c.text, }))
}
match doc.feature {
Some(f) => emit_feature(events, f)
None => ()
}
events.push(DocumentEnd)
{ events, pos: 0, }
}
///|
/// Return the next event, advancing the reader position.
/// Returns `None` when all events have been consumed.
pub fn GherkinReader::next(self : GherkinReader) -> GherkinEvent? {
if self.pos < self.events.length() {
let e = self.events[self.pos]
self.pos = self.pos + 1
Some(e)
} else {
None
}
}
///|
/// Return the next event without advancing the reader position.
/// Returns `None` when all events have been consumed.
pub fn GherkinReader::peek(self : GherkinReader) -> GherkinEvent? {
if self.pos < self.events.length() {
Some(self.events[self.pos])
} else {
None
}
}
///|
/// Return an `Iter[GherkinEvent]` that lazily pulls events.
pub fn GherkinReader::iter(self : GherkinReader) -> Iter[GherkinEvent] {
Iter::new(fn() { self.next() })
}
///|
/// 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.
pub fn GherkinReader::pipe(
self : GherkinReader,
handler : &GherkinHandler,
) -> Unit {
for e in self.iter() {
match e {
DocumentStart => handler.on_document()
DocumentEnd => handler.on_end_document()
FeatureStart(fe) => handler.on_feature(fe)
FeatureEnd => handler.on_end_feature()
RuleStart(re) => handler.on_rule(re)
RuleEnd => handler.on_end_rule()
BackgroundStart(be) => handler.on_background(be)
BackgroundEnd => handler.on_end_background()
ScenarioStart(se) => handler.on_scenario(se)
ScenarioEnd => handler.on_end_scenario()
Step(se) => handler.on_step(se)
DocString(de) => handler.on_doc_string(de)
DataTable(dte) => handler.on_data_table(dte)
Examples(ee) => handler.on_examples(ee)
Tag(te) => handler.on_tag(te)
Comment(ce) => handler.on_comment(ce)
}
}
}
// ---------------------------------------------------------------------------
// Private: AST walk to build event buffer
// ---------------------------------------------------------------------------
///|
fn emit_feature(events : Array[GherkinEvent], f : Feature) -> Unit {
events.push(
FeatureStart({
location: f.location,
tags: f.tags,
language: f.language,
keyword: f.keyword,
name: f.name,
description: f.description,
}),
)
for t in f.tags {
events.push(Tag({ location: t.location, name: t.name, }))
}
for child in f.children {
match child {
FeatureChild::Background(bg) => emit_background(events, bg)
FeatureChild::Scenario(s) => emit_scenario(events, s)
FeatureChild::Rule(r) => emit_rule(events, r)
}
}
events.push(FeatureEnd)
}
///|
fn emit_rule(events : Array[GherkinEvent], r : Rule) -> Unit {
events.push(
RuleStart({
location: r.location,
tags: r.tags,
keyword: r.keyword,
name: r.name,
description: r.description,
}),
)
for t in r.tags {
events.push(Tag({ location: t.location, name: t.name, }))
}
for child in r.children {
match child {
RuleChild::Background(bg) => emit_background(events, bg)
RuleChild::Scenario(s) => emit_scenario(events, s)
}
}
events.push(RuleEnd)
}
///|
fn emit_background(events : Array[GherkinEvent], bg : Background) -> Unit {
events.push(
BackgroundStart({
location: bg.location,
keyword: bg.keyword,
name: bg.name,
description: bg.description,
}),
)
for s in bg.steps {
emit_step(events, s)
}
events.push(BackgroundEnd)
}
///|
fn emit_scenario(events : Array[GherkinEvent], s : Scenario) -> Unit {
events.push(
ScenarioStart({
location: s.location,
tags: s.tags,
kind: s.kind,
keyword: s.keyword,
name: s.name,
description: s.description,
}),
)
for t in s.tags {
events.push(Tag({ location: t.location, name: t.name, }))
}
for step in s.steps {
emit_step(events, step)
}
for e in s.examples {
events.push(
Examples({
location: e.location,
tags: e.tags,
keyword: e.keyword,
name: e.name,
description: e.description,
table_header: e.table_header,
table_body: e.table_body,
}),
)
}
events.push(ScenarioEnd)
}
///|
fn emit_step(events : Array[GherkinEvent], s : Step) -> Unit {
events.push(
Step({
location: s.location,
keyword: s.keyword,
keyword_type: s.keyword_type,
text: s.text,
}),
)
match s.argument {
Some(StepArgument::DocString(ds)) =>
events.push(
DocString({
location: ds.location,
media_type: ds.media_type,
content: ds.content,
delimiter: ds.delimiter,
}),
)
Some(StepArgument::DataTable(dt)) =>
events.push(DataTable({ location: dt.location, rows: dt.rows, }))
None => ()
}
}