///|
/// 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
// `SkipChildren` and `Stop` are produced by user callbacks; only `Continue` is
// built inside this package.
#warnings("-unused_constructor")
pub enum FoldAction[A] {
Continue(A)
SkipChildren(A)
Stop(A)
} derive(Debug, Eq)
///|
/// Extract the accumulated value from a `FoldAction`.
pub fn[A] FoldAction::value(self : FoldAction[A]) -> A {
match self {
Continue(a) => a
SkipChildren(a) => a
Stop(a) => a
}
}
///|
/// Returns `true` if this action is `Stop`.
pub fn[A] FoldAction::is_stop(self : FoldAction[A]) -> Bool {
match self {
Stop(_) => true
_ => false
}
}
///|
/// 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) }
/// })
/// ```
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]
}
///|
/// Create a `GherkinFold` where every callback passes state through unchanged.
pub fn[A] GherkinFold::default() -> GherkinFold[A] {
{
visit_document: fn(a, _) { Continue(a) },
visit_feature: fn(a, _) { Continue(a) },
visit_rule: fn(a, _) { Continue(a) },
visit_background: fn(a, _) { Continue(a) },
visit_scenario: fn(a, _) { Continue(a) },
visit_step: fn(a, _) { Continue(a) },
visit_doc_string: fn(a, _) { Continue(a) },
visit_data_table: fn(a, _) { Continue(a) },
visit_examples: fn(a, _) { Continue(a) },
visit_tag: fn(a, _) { Continue(a) },
visit_comment: fn(a, _) { Continue(a) },
visit_table_row: fn(a, _) { Continue(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 })
/// ```
pub fn[A, N] continuing(f : (A, N) -> A) -> (A, N) -> FoldAction[A] {
fn(a, n) { Continue(f(a, n)) }
}
///|
/// 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
pub fn[A] GherkinDocument::fold(
self : GherkinDocument,
init : A,
folder : GherkinFold[A],
) -> A {
let mut acc = init
match (folder.visit_document)(acc, self) {
Stop(a) => return a
SkipChildren(a) => return a
Continue(a) => acc = a
}
for c in self.comments {
match (folder.visit_comment)(acc, c) {
Stop(a) => return a
Continue(a) | SkipChildren(a) => acc = a
}
}
match self.feature {
Some(f) => acc = fold_feature(acc, f, folder)
None => ()
}
acc
}
///|
fn[A] fold_feature(init : A, f : Feature, folder : GherkinFold[A]) -> A {
let mut acc = init
match (folder.visit_feature)(acc, f) {
Stop(a) => return a
SkipChildren(a) => return a
Continue(a) => acc = a
}
for t in f.tags {
match (folder.visit_tag)(acc, t) {
Stop(a) => return a
Continue(a) | SkipChildren(a) => acc = a
}
}
for child in f.children {
match child {
FeatureChild::Background(bg) => acc = fold_background(acc, bg, folder)
FeatureChild::Scenario(s) => acc = fold_scenario(acc, s, folder)
FeatureChild::Rule(r) => acc = fold_rule(acc, r, folder)
}
}
acc
}
///|
fn[A] fold_rule(init : A, r : Rule, folder : GherkinFold[A]) -> A {
let mut acc = init
match (folder.visit_rule)(acc, r) {
Stop(a) => return a
SkipChildren(a) => return a
Continue(a) => acc = a
}
for t in r.tags {
match (folder.visit_tag)(acc, t) {
Stop(a) => return a
Continue(a) | SkipChildren(a) => acc = a
}
}
for child in r.children {
match child {
RuleChild::Background(bg) => acc = fold_background(acc, bg, folder)
RuleChild::Scenario(s) => acc = fold_scenario(acc, s, folder)
}
}
acc
}
///|
fn[A] fold_background(init : A, bg : Background, folder : GherkinFold[A]) -> A {
let mut acc = init
match (folder.visit_background)(acc, bg) {
Stop(a) => return a
SkipChildren(a) => return a
Continue(a) => acc = a
}
for s in bg.steps {
acc = fold_step(acc, s, folder)
}
acc
}
///|
fn[A] fold_scenario(init : A, s : Scenario, folder : GherkinFold[A]) -> A {
let mut acc = init
match (folder.visit_scenario)(acc, s) {
Stop(a) => return a
SkipChildren(a) => return a
Continue(a) => acc = a
}
for t in s.tags {
match (folder.visit_tag)(acc, t) {
Stop(a) => return a
Continue(a) | SkipChildren(a) => acc = a
}
}
for step in s.steps {
acc = fold_step(acc, step, folder)
}
for e in s.examples {
acc = fold_examples(acc, e, folder)
}
acc
}
///|
fn[A] fold_step(init : A, s : Step, folder : GherkinFold[A]) -> A {
let mut acc = init
match (folder.visit_step)(acc, s) {
Stop(a) => return a
SkipChildren(a) => return a
Continue(a) => acc = a
}
match s.argument {
Some(StepArgument::DocString(ds)) =>
match (folder.visit_doc_string)(acc, ds) {
Stop(a) => return a
Continue(a) | SkipChildren(a) => acc = a
}
Some(StepArgument::DataTable(dt)) =>
match (folder.visit_data_table)(acc, dt) {
Stop(a) => return a
SkipChildren(a) => acc = a
Continue(a) => {
acc = a
for r in dt.rows {
match (folder.visit_table_row)(acc, r) {
Stop(a) => return a
Continue(a) | SkipChildren(a) => acc = a
}
}
}
}
None => ()
}
acc
}
///|
fn[A] fold_examples(init : A, e : Examples, folder : GherkinFold[A]) -> A {
let mut acc = init
match (folder.visit_examples)(acc, e) {
Stop(a) => return a
SkipChildren(a) => return a
Continue(a) => acc = a
}
for t in e.tags {
match (folder.visit_tag)(acc, t) {
Stop(a) => return a
Continue(a) | SkipChildren(a) => acc = a
}
}
match e.table_header {
Some(h) =>
match (folder.visit_table_row)(acc, h) {
Stop(a) => return a
Continue(a) | SkipChildren(a) => acc = a
}
None => ()
}
for r in e.table_body {
match (folder.visit_table_row)(acc, r) {
Stop(a) => return a
Continue(a) | SkipChildren(a) => acc = a
}
}
acc
}
///|
pub extend FoldAction with @moonbitlang/core/debug.Debug::{to_repr}
///|
pub extend FoldAction with Eq::{not_equal, equal}