///|
/// Core parser combinators: monadic bind, choice, and structural helpers.
///
/// Provides `bind`, `map`, `then`, `skip`, `or`, `attempt`, `optional`,
/// `label`, `lexeme`, `between`, `not_followed_by`, `choice`, and their
/// standalone variants. All combinators respect the commit mechanism for
/// commit-aware backtracking.
///|
/// Monadic bind. Runs `self`, passes the result to `next`.
///
/// Parameters:
///
/// * `self` : The first parser to run.
/// * `next` : A function that takes the result of `self` and returns
/// the next parser to run.
///
/// If `self` consumed input, any error from `next` is marked committed,
/// preventing backtracking across consumed input.
///
/// Returns a parser that runs `self`, then feeds its result to `next`.
pub fn[I : Cursor, T, U, E : Commit] ParserRaw::bind(
self : ParserRaw[I, T, E],
next : (T) -> ParserRaw[I, U, E],
) -> ParserRaw[I, U, E] {
ParserRaw::new(input => {
self
.run(input)
.bind(result => {
let (value, rest) = result
next(value)
.run(rest)
.map_err(error => {
guard rest.cursor() != input.cursor() else { error }
Commit::mark_committed(error)
})
})
})
}
///|
/// Transforms the result of a successful parse using `f`.
///
/// Parameters:
///
/// * `self` : The parser whose result to transform.
/// * `f` : The transformation function.
///
/// Returns a parser that parses the same input as `self`
/// and applies `f` to the result on success.
#inline
pub fn[I : Cursor, T, U, E : Commit] ParserRaw::map(
self : ParserRaw[I, T, E],
f : (T) -> U,
) -> ParserRaw[I, U, E] {
self.bind(value => pure(f(value)))
}
///|
/// Runs `self` then `next`, discarding `self`'s result.
///
/// Parameters:
///
/// * `self` : The first parser; its result is discarded.
/// * `next` : The second parser; its result is returned.
///
/// Equivalent to `self >> next`.
#inline
pub fn[I : Cursor, T, U, E : Commit] ParserRaw::then(
self : ParserRaw[I, T, E],
next : ParserRaw[I, U, E],
) -> ParserRaw[I, U, E] {
self.bind(_ => next)
}
///|
/// Runs `self` then `next`, returning `self`'s result.
///
/// Parameters:
///
/// * `self` : The first parser; its result is returned.
/// * `next` : The second parser; its result is discarded.
///
/// Equivalent to `self <* next`.
#inline
pub fn[I : Cursor, T, U, E : Commit] ParserRaw::skip(
self : ParserRaw[I, T, E],
next : ParserRaw[I, U, E],
) -> ParserRaw[I, T, E] {
self.bind(value => next.map(_ => value))
}
///|
/// Choice with commit-aware backtracking.
///
/// Parameters:
///
/// * `self` : The first alternative.
/// * `other` : The second alternative, tried if `self` fails
/// without committing.
///
/// If `self` fails with committed error, `other` is NOT tried.
/// Errors are merged by furthest position.
///
/// Returns the result of the first successful alternative.
pub fn[I, T, E : Commit + CanMerge] ParserRaw::or(
self : ParserRaw[I, T, E],
other : ParserRaw[I, T, E],
) -> ParserRaw[I, T, E] {
ParserRaw::new(input => {
match self.run(input) {
Ok((value, rest)) => Ok((value, rest))
Err(first) if Commit::is_committed(first) => Err(first)
Err(first) =>
other.run(input).map_err(second => CanMerge::merge(first, second))
}
})
}
///|
/// Strips the committed flag from any error.
///
/// Parameters:
///
/// * `self` : The parser to wrap.
///
/// Enables backtracking across input consumed by `self`.
/// Use sparingly — prefer grammars where alternatives are
/// distinguishable without backtracking.
#inline
pub fn[I, T, E : Commit] ParserRaw::attempt(
self : ParserRaw[I, T, E],
) -> ParserRaw[I, T, E] {
ParserRaw::new(input => {
self.run(input).map_err(error => Commit::clear_commit(error))
})
}
///|
/// Runs `self` and returns `Some(result)` on success.
///
/// Parameters:
///
/// * `self` : The parser to try.
///
/// Returns `None` on non-committed failure. Committed failures
/// propagate immediately.
pub fn[I, T, E : Commit] ParserRaw::optional(
self : ParserRaw[I, T, E],
) -> ParserRaw[I, T?, E] {
ParserRaw::new(input => {
match self.run(input) {
Ok((value, rest)) => Ok((Some(value), rest))
Err(error) => Commit::reject_if_committed(error, None, input)
}
})
}
///|
/// Replaces the expected label in the error if `self` fails
/// without having advanced the cursor.
///
/// Parameters:
///
/// * `self` : The parser to label.
/// * `expected` : The label to report on failure.
///
/// If input was consumed before failure, the original error
/// is preserved (furthest position wins). A replaced label
/// keeps the original error's committed flag.
pub fn[I : Cursor, T, E : ParseFailure] ParserRaw::label(
self : ParserRaw[I, T, E],
expected : String,
) -> ParserRaw[I, T, E] {
ParserRaw::new(input => {
self
.run(input)
.map_err(error => {
guard Positioned::error_offset(error) == input.cursor() else { error }
let labeled = ParseFailure::signal(input, expected)
guard Commit::is_committed(error) else { labeled }
Commit::mark_committed(labeled)
})
})
}
///|
/// Wraps `self` as a lexeme by skipping trailing whitespace.
///
/// Parameters:
///
/// * `self` : The parser to wrap.
///
/// Returns a parser that runs `self` then skips whitespace.
#inline
pub fn[T] ParserRaw::lexeme(self : Parser[Input, T]) -> Parser[Input, T] {
self.skip(spaces())
}
///|
/// Runs `left`, then `self`, then `right`, returning `self`'s result.
///
/// Parameters:
///
/// * `self` : The middle parser whose result is returned.
/// * `left` : The opening parser; its result is discarded.
/// * `right` : The closing parser; its result is discarded.
///
/// Equivalent to `left.then(self).skip(right)`.
#inline
pub fn[I : Cursor, T, L, R, E : Commit] ParserRaw::between(
self : ParserRaw[I, T, E],
left : ParserRaw[I, L, E],
right : ParserRaw[I, R, E],
) -> ParserRaw[I, T, E] {
left.then(self).skip(right)
}
///|
/// Runs `left` then `self`, returning `self`'s result.
///
/// Parameters:
///
/// * `self` : The parser whose result is returned.
/// * `left` : The parser whose result is discarded.
///
/// Equivalent to `left *> self`.
#inline
pub fn[I : Cursor, T, L, E : Commit] ParserRaw::after(
self : ParserRaw[I, T, E],
left : ParserRaw[I, L, E],
) -> ParserRaw[I, T, E] {
left.then(self)
}
///|
/// Runs `self` then `right`, returning `self`'s result.
///
/// Parameters:
///
/// * `self` : The parser whose result is returned.
/// * `right` : The parser whose result is discarded.
///
/// Equivalent to `self <* right`.
#inline
pub fn[I : Cursor, T, R, E : Commit] ParserRaw::before(
self : ParserRaw[I, T, E],
right : ParserRaw[I, R, E],
) -> ParserRaw[I, T, E] {
self.skip(right)
}
///|
/// Standalone version of `Parser::lexeme`.
#inline
pub fn[T] lexeme(parser : Parser[Input, T]) -> Parser[Input, T] {
parser.lexeme()
}
///|
/// Matches an exact string, then skips trailing whitespace.
#inline
pub fn symbol(text : String) -> Parser[Input, String] {
string(text).lexeme()
}
///|
/// Negative lookahead. Succeeds with `()` (consuming no input)
/// when `self` fails.
#inline
pub fn[I : Cursor, T, E : ParseFailure] ParserRaw::not_followed_by(
self : ParserRaw[I, T, E],
label : String,
) -> ParserRaw[I, Unit, E] {
ParserRaw::new(input => {
match self.run(input) {
Ok(_) => Err(ParseFailure::message(input, "unexpected \{label}"))
Err(_) => Ok(((), input))
}
})
}
///|
/// Tries each parser in the array in order on the same input.
///
/// Iterates through parsers, accumulating errors via `CanMerge::merge`.
/// On committed failure, propagation is immediate — no further alternatives
/// are tried. If no parser succeeds, the merged error (or a fallback message
/// for an empty array) is returned.
#inline
pub fn[I : Cursor, T, E : Commit + CanMerge + ParseFailure] choice(
parsers : Array[ParserRaw[I, T, E]],
) -> ParserRaw[I, T, E] {
ParserRaw::new(input => {
let mut merged : E? = None
for i = 0; i < parsers.length(); i = i + 1 {
match parsers[i].run(input) {
Ok(result) => break Ok(result)
Err(error) => {
guard !Commit::is_committed(error) else { break Err(error) }
// Accumulate: merge the latest error into the running combination.
merged = match merged {
Some(prev) => Some(CanMerge::merge(prev, error))
None => Some(error)
}
continue
}
}
} nobreak {
match merged {
Some(err) => Err(err)
None => Err(ParseFailure::message(input, "empty choice"))
}
}
})
}
///|
/// Standalone version of `Parser::between`.
#inline
pub fn[I : Cursor, T, L, R, E : Commit] between(
left : ParserRaw[I, L, E],
parser : ParserRaw[I, T, E],
right : ParserRaw[I, R, E],
) -> ParserRaw[I, T, E] {
parser.between(left, right)
}