Choice Parser
Tutorial
The Problem
Grammars have alternatives: a value is either a number, a string, a boolean, or null. The alt (or choice) combinator tries parsers in order, returning the first success. If all fail, it returns the last error. This ordered choice is the basis for all ambiguity resolution in parsing — the order of alternatives determines precedence. Getting alternatives right (trying most specific before most general) is key to correct parsers.
🎯 Learning Outcomes
alt/choice as ordered alternative (PEG-style, not CFG-style)choice([tag("if"), identifier]) vs. choice([identifier, tag("if")])choiceCode Example
fn alt<'a, T: 'a>(p1: Parser<'a, T>, p2: Parser<'a, T>) -> Parser<'a, T> {
Box::new(move |input: &'a str| match p1(input) {
Ok(result) => Ok(result),
Err(_) => p2(input),
})
}Key Differences
choice always backtracks fully; angstrom's <|> does not backtrack after consuming input (requires explicit option/try).OCaml Approach
OCaml's angstrom uses <|> as the choice operator:
let value = int_parser <|> string_parser <|> bool_parser <|> null_parser
Angstrom's choice is greedy and does not backtrack by default — if the first parser consumes input and then fails, the alternative is not tried. This is a key difference from Parsec/Rust's simple implementation. Backtracking requires wrapping the parser in try / option.
Full Source
#![allow(clippy::all)]
// Example 157: Choice Parser
// alt / choice: try parsers in order, return first success
type ParseResult<'a, T> = Result<(T, &'a str), String>;
type Parser<'a, T> = Box<dyn Fn(&'a str) -> ParseResult<'a, T> + 'a>;
fn tag<'a>(expected: &str) -> Parser<'a, &'a str> {
let exp = expected.to_string();
Box::new(move |input: &'a str| {
if input.starts_with(&exp) {
Ok((&input[..exp.len()], &input[exp.len()..]))
} else {
Err(format!("Expected \"{}\"", exp))
}
})
}
fn satisfy<'a, F>(pred: F, desc: &str) -> Parser<'a, char>
where
F: Fn(char) -> bool + 'a,
{
let desc = desc.to_string();
Box::new(move |input: &'a str| match input.chars().next() {
Some(c) if pred(c) => Ok((c, &input[c.len_utf8()..])),
_ => Err(format!("Expected {}", desc)),
})
}
// ============================================================
// Approach 1: alt — try two parsers
// ============================================================
fn alt<'a, T: 'a>(p1: Parser<'a, T>, p2: Parser<'a, T>) -> Parser<'a, T> {
Box::new(move |input: &'a str| match p1(input) {
Ok(result) => Ok(result),
Err(_) => p2(input),
})
}
// ============================================================
// Approach 2: choice — try a list of parsers
// ============================================================
fn choice<'a, T: 'a>(parsers: Vec<Parser<'a, T>>) -> Parser<'a, T> {
Box::new(move |input: &'a str| {
for parser in &parsers {
if let Ok(result) = parser(input) {
return Ok(result);
}
}
Err("No parser matched".to_string())
})
}
// ============================================================
// Approach 3: alt with error accumulation
// ============================================================
fn alt_err<'a, T: 'a>(p1: Parser<'a, T>, p2: Parser<'a, T>) -> Parser<'a, T> {
Box::new(move |input: &'a str| match p1(input) {
Ok(result) => Ok(result),
Err(e1) => match p2(input) {
Ok(result) => Ok(result),
Err(e2) => Err(format!("{} or {}", e1, e2)),
},
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_alt_first() {
let p = alt(tag("true"), tag("false"));
assert_eq!(p("true!"), Ok(("true", "!")));
}
#[test]
fn test_alt_second() {
let p = alt(tag("true"), tag("false"));
assert_eq!(p("false!"), Ok(("false", "!")));
}
#[test]
fn test_alt_neither() {
let p = alt(tag("true"), tag("false"));
assert!(p("maybe").is_err());
}
#[test]
fn test_choice_first() {
let p = choice(vec![tag("a"), tag("b"), tag("c")]);
assert_eq!(p("abc"), Ok(("a", "bc")));
}
#[test]
fn test_choice_last() {
let p = choice(vec![tag("x"), tag("y"), tag("z")]);
assert_eq!(p("zoo"), Ok(("z", "oo")));
}
#[test]
fn test_choice_none() {
let p = choice(vec![tag("x"), tag("y")]);
assert!(p("abc").is_err());
}
#[test]
fn test_choice_empty() {
let p: Parser<&str> = choice(vec![]);
assert!(p("abc").is_err());
}
#[test]
fn test_alt_err_accumulates() {
let p = alt_err(
satisfy(|c| c.is_ascii_digit(), "digit"),
satisfy(|c| c.is_ascii_alphabetic(), "letter"),
);
let err = p("!x").unwrap_err();
assert!(err.contains("digit"));
assert!(err.contains("letter"));
}
}#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_alt_first() {
let p = alt(tag("true"), tag("false"));
assert_eq!(p("true!"), Ok(("true", "!")));
}
#[test]
fn test_alt_second() {
let p = alt(tag("true"), tag("false"));
assert_eq!(p("false!"), Ok(("false", "!")));
}
#[test]
fn test_alt_neither() {
let p = alt(tag("true"), tag("false"));
assert!(p("maybe").is_err());
}
#[test]
fn test_choice_first() {
let p = choice(vec![tag("a"), tag("b"), tag("c")]);
assert_eq!(p("abc"), Ok(("a", "bc")));
}
#[test]
fn test_choice_last() {
let p = choice(vec![tag("x"), tag("y"), tag("z")]);
assert_eq!(p("zoo"), Ok(("z", "oo")));
}
#[test]
fn test_choice_none() {
let p = choice(vec![tag("x"), tag("y")]);
assert!(p("abc").is_err());
}
#[test]
fn test_choice_empty() {
let p: Parser<&str> = choice(vec![]);
assert!(p("abc").is_err());
}
#[test]
fn test_alt_err_accumulates() {
let p = alt_err(
satisfy(|c| c.is_ascii_digit(), "digit"),
satisfy(|c| c.is_ascii_alphabetic(), "letter"),
);
let err = p("!x").unwrap_err();
assert!(err.contains("digit"));
assert!(err.contains("letter"));
}
}
Deep Comparison
Comparison: Example 157 — Choice Parser
alt
OCaml:
let alt (p1 : 'a parser) (p2 : 'a parser) : 'a parser = fun input ->
match p1 input with
| Ok _ as result -> result
| Error _ -> p2 input
Rust:
fn alt<'a, T: 'a>(p1: Parser<'a, T>, p2: Parser<'a, T>) -> Parser<'a, T> {
Box::new(move |input: &'a str| match p1(input) {
Ok(result) => Ok(result),
Err(_) => p2(input),
})
}
choice
OCaml:
let choice (parsers : 'a parser list) : 'a parser = fun input ->
let rec try_parsers = function
| [] -> Error "No parser matched"
| p :: rest ->
match p input with
| Ok _ as result -> result
| Error _ -> try_parsers rest
in
try_parsers parsers
Rust:
fn choice<'a, T: 'a>(parsers: Vec<Parser<'a, T>>) -> Parser<'a, T> {
Box::new(move |input: &'a str| {
for parser in &parsers {
if let Ok(result) = parser(input) {
return Ok(result);
}
}
Err("No parser matched".to_string())
})
}
Exercises
json_value_parser using choice that handles null, true/false, numbers, and strings.choice([identifier, tag("true")]) treats "true" as an identifier, while choice([tag("true"), identifier]) parses it as a boolean.