extern crate alloc;
use alloc::string::String;
use alloc::vec::Vec;
use core::ops::ControlFlow;
use cocoro::Coro;
use cocoro::Return;
use cocoro::Suspended;
use cocoro::Yield;
use cocoro::from_control_flow;
use cocoro::take;
#[derive(Debug, Clone, PartialEq)]
pub struct CsvRecord {
pub fields: Vec<String>,
pub line_number: usize,
}
pub fn parse_csv(input: &str) -> impl Coro<(), CsvRecord, ()> + '_ {
let mut chars = input.chars();
let mut line_number = 1;
let mut current_field = String::new();
let mut current_record = Vec::new();
let mut in_quotes = false;
from_control_flow(move |_: ()| {
use ControlFlow::Break;
use ControlFlow::Continue;
loop {
let ch = match chars.next() {
Some(ch) => ch,
None => {
if !current_field.is_empty() || !current_record.is_empty() {
current_record.push(current_field.clone());
let record = CsvRecord {
fields: current_record.clone(),
line_number,
};
current_field.clear();
current_record.clear();
return Continue(record);
} else {
return Break(());
}
}
};
match ch {
'"' => {
in_quotes = !in_quotes;
}
',' if !in_quotes => {
current_record.push(current_field.clone());
current_field.clear();
}
'\n' if !in_quotes => {
current_record.push(current_field.clone());
current_field.clear();
let record = CsvRecord {
fields: current_record.clone(),
line_number,
};
current_record.clear();
line_number += 1;
return Continue(record);
}
'\r' if !in_quotes => {
}
_ => {
current_field.push(ch);
}
}
}
})
}
fn main() {
println!("CSV Parser Example using cocoro coroutines\n");
let csv_data = r#"Name,Age,City
"John Doe",30,New York
"Jane Smith",25,"Los Angeles"
Bob Johnson,35,Chicago
"Mary Wilson",28,Boston"#;
println!("Input CSV data:");
println!("{csv_data}\n");
println!("Example 1: Basic parsing with for_each:");
parse_csv(csv_data).for_each(|record| {
println!(" Line {}: {:?}", record.line_number, record.fields);
});
println!(" Result: {:?}\n", ());
println!(
"Example 2: Manual iteration with explicit yield/return handling:"
);
fn manual_iterate<C: Coro<(), CsvRecord, ()>>(parser: C, count: usize) {
let suspended = parser.resume(());
match suspended.into_enum() {
Yield(record, next) => {
println!(" Record {}: {:?}", count + 1, record.fields);
manual_iterate(next, count + 1);
}
Return(result) => {
println!(" Parser finished with: {result:?}");
println!(" Processed {count} records manually\n");
}
}
}
manual_iterate(parse_csv(csv_data), 0);
println!("Example 3: Combinator chaining - filtering people over 30:");
let with_age_info = parse_csv(csv_data).map_yield(|record| {
if record.line_number == 1 {
return (record, None); }
let age = if record.fields.len() >= 2 {
record.fields[1].parse::<u32>().ok()
} else {
None
};
(record, age)
});
with_age_info.for_each(|(record, age_opt)| {
if let Some(age) = age_opt {
if age > 30 {
println!(" Over 30: {:?} (age: {})", record.fields, age);
}
}
});
println!(" Finished filtering records\n");
println!("Example 4: Using take() combinator to limit output:");
let result = parse_csv(csv_data)
.map_return(|r| format!("Original parser would have returned {r:?}"))
.compose(
take(3).map_return(|()| "Stopped after taking 3 records".into()),
)
.for_each(|record| {
println!(
" Limited: Line {}: {:?}",
record.line_number, record.fields
);
});
println!("{result}");
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_simple_csv_parsing() {
let csv = "a,b,c\n1,2,3";
let parser = parse_csv(csv);
let suspended = parser.resume(());
if let Yield(record, next) = suspended.into_enum() {
assert_eq!(record.fields, vec!["a", "b", "c"]);
assert_eq!(record.line_number, 1);
let suspended2 = next.resume(());
if let Yield(record, next2) = suspended2.into_enum() {
assert_eq!(record.fields, vec!["1", "2", "3"]);
assert_eq!(record.line_number, 2);
let suspended3 = next2.resume(());
if let Return(result) = suspended3.into_enum() {
assert_eq!(result, ());
}
}
}
}
#[test]
fn test_quoted_fields() {
let csv = r#""hello, world",test"#;
let parser = parse_csv(csv);
let suspended = parser.resume(());
if let Yield(record, _) = suspended.into_enum() {
assert_eq!(record.fields, vec!["hello, world", "test"]);
}
}
#[test]
fn test_for_each_combinator() {
let csv = "a,b\n1,2\n3,4";
let parser = parse_csv(csv);
let mut records = Vec::new();
let result = parser.for_each(|record| {
records.push(record);
});
assert_eq!(records.len(), 3);
assert_eq!(records[0].fields, vec!["a", "b"]);
assert_eq!(records[1].fields, vec!["1", "2"]);
assert_eq!(records[2].fields, vec!["3", "4"]);
assert_eq!(result, ());
}
#[test]
fn test_empty_csv() {
let csv = "";
let parser = parse_csv(csv);
let suspended = parser.resume(());
if let Return(result) = suspended.into_enum() {
assert_eq!(result, ());
}
}
#[test]
fn test_take_combinator() {
let csv = "a,b\n1,2\n3,4\n5,6";
let parser = parse_csv(csv);
let mut records = Vec::new();
let result = parser.take(2).for_each(|record| {
records.push(record);
});
assert_eq!(records.len(), 2);
assert_eq!(records[0].fields, vec!["a", "b"]);
assert_eq!(records[1].fields, vec!["1", "2"]);
assert_eq!(result, None);
}
#[test]
fn test_parser_state_advancement() {
let csv = "a,b\nc,d";
let parser = parse_csv(csv);
let suspended1 = parser.resume(());
if let Yield(record1, next) = suspended1.into_enum() {
assert_eq!(record1.fields, vec!["a", "b"]);
assert_eq!(record1.line_number, 1);
let suspended2 = next.resume(());
if let Yield(record2, next2) = suspended2.into_enum() {
assert_eq!(record2.fields, vec!["c", "d"]);
assert_eq!(record2.line_number, 2);
assert_ne!(record1, record2);
let suspended3 = next2.resume(());
if let Return(result) = suspended3.into_enum() {
assert_eq!(result, ());
} else {
panic!("Expected parser to return after last record");
}
} else {
panic!("Expected second yield, got return");
}
} else {
panic!("Expected first yield, got return");
}
}
#[test]
fn test_infinite_loop_prevention() {
let csv = "header\nrow1\nrow2";
let parser = parse_csv(csv);
let mut seen_records = Vec::new();
let mut iterations = 0;
let max_iterations = 10;
fn check_advancement<C: Coro<(), CsvRecord, ()>>(
parser: C,
seen_records: &mut Vec<CsvRecord>,
iterations: &mut usize,
max_iterations: usize,
) -> bool {
if *iterations >= max_iterations {
return false; }
*iterations += 1;
let suspended = parser.resume(());
match suspended.into_enum() {
Yield(record, next) => {
if seen_records.contains(&record) {
return false; }
seen_records.push(record);
check_advancement(
next,
seen_records,
iterations,
max_iterations,
)
}
Return(_) => {
true }
}
}
let success = check_advancement(
parser,
&mut seen_records,
&mut iterations,
max_iterations,
);
assert!(success, "Parser got stuck or hit infinite loop");
assert_eq!(seen_records.len(), 3); assert!(
iterations < max_iterations,
"Parser took too many iterations"
);
assert_eq!(seen_records[0].fields, vec!["header"]);
assert_eq!(seen_records[1].fields, vec!["row1"]);
assert_eq!(seen_records[2].fields, vec!["row2"]);
assert_eq!(seen_records[0].line_number, 1);
assert_eq!(seen_records[1].line_number, 2);
assert_eq!(seen_records[2].line_number, 3);
}
}