#![allow(dead_code)]
#[allow(dead_code)]
#[derive(Debug, Clone, PartialEq)]
pub enum ParseNode {
Literal(String),
Sequence(Vec<ParseNode>),
Choice(Box<ParseNode>),
Empty,
}
#[allow(dead_code)]
pub type ParseResult<'a> = Option<(ParseNode, &'a str)>;
#[allow(dead_code)]
pub fn parse_literal<'a>(input: &'a str, lit: &str) -> ParseResult<'a> {
if let Some(rest) = input.strip_prefix(lit) {
Some((ParseNode::Literal(lit.to_string()), rest))
} else {
None
}
}
#[allow(dead_code)]
pub fn skip_whitespace(input: &str) -> &str {
input.trim_start()
}
#[allow(dead_code)]
pub fn parse_integer(input: &str) -> ParseResult<'_> {
let s = skip_whitespace(input);
let end = s.find(|c: char| !c.is_ascii_digit()).unwrap_or(s.len());
if end == 0 {
return None;
}
Some((ParseNode::Literal(s[..end].to_string()), &s[end..]))
}
#[allow(dead_code)]
pub fn parse_ident(input: &str) -> ParseResult<'_> {
let s = skip_whitespace(input);
let end = s
.find(|c: char| !c.is_alphanumeric() && c != '_')
.unwrap_or(s.len());
if end == 0 {
return None;
}
Some((ParseNode::Literal(s[..end].to_string()), &s[end..]))
}
#[allow(dead_code)]
pub fn parse_opt<'a, F>(input: &'a str, f: F) -> (ParseNode, &'a str)
where
F: Fn(&'a str) -> ParseResult<'a>,
{
if let Some((n, rest)) = f(input) {
(n, rest)
} else {
(ParseNode::Empty, input)
}
}
#[allow(dead_code)]
pub fn parse_choice<'a, F, G>(input: &'a str, f: F, g: G) -> ParseResult<'a>
where
F: Fn(&'a str) -> ParseResult<'a>,
G: Fn(&'a str) -> ParseResult<'a>,
{
f(input).or_else(|| g(input))
}
#[allow(dead_code)]
pub fn parse_list<'a>(input: &'a str, delim: &str) -> (Vec<String>, &'a str) {
let mut items = Vec::new();
let mut cur = skip_whitespace(input);
while let Some((ParseNode::Literal(s), rest)) = parse_ident(cur) {
items.push(s);
let rest2 = skip_whitespace(rest);
if let Some(after_delim) = rest2.strip_prefix(delim) {
cur = skip_whitespace(after_delim);
} else {
cur = rest2;
break;
}
}
(items, cur)
}
#[allow(dead_code)]
pub fn parse_depth(input: &str) -> usize {
let mut depth = 0usize;
let mut max_depth = 0usize;
for c in input.chars() {
match c {
'(' | '[' | '{' => {
depth += 1;
if depth > max_depth {
max_depth = depth;
}
}
')' | ']' | '}' => {
depth = depth.saturating_sub(1);
}
_ => {}
}
}
max_depth
}
#[allow(dead_code)]
pub fn node_text(n: &ParseNode) -> Option<&str> {
if let ParseNode::Literal(s) = n {
Some(s.as_str())
} else {
None
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_literal_success() {
let (node, rest) = parse_literal("hello world", "hello").expect("should succeed");
assert_eq!(node_text(&node), Some("hello"));
assert_eq!(rest, " world");
}
#[test]
fn test_parse_literal_fail() {
assert!(parse_literal("world", "hello").is_none());
}
#[test]
fn test_parse_integer() {
let (node, rest) = parse_integer("123abc").expect("should succeed");
assert_eq!(node_text(&node), Some("123"));
assert_eq!(rest, "abc");
}
#[test]
fn test_parse_ident() {
let (node, rest) = parse_ident("foo_bar 42").expect("should succeed");
assert_eq!(node_text(&node), Some("foo_bar"));
assert_eq!(rest, " 42");
}
#[test]
fn test_parse_opt_success() {
let (node, _rest) = parse_opt("hello", |i| parse_literal(i, "hello"));
assert_ne!(node, ParseNode::Empty);
}
#[test]
fn test_parse_opt_fail() {
let (node, rest) = parse_opt("world", |i| parse_literal(i, "hello"));
assert_eq!(node, ParseNode::Empty);
assert_eq!(rest, "world");
}
#[test]
fn test_parse_choice() {
let r = parse_choice("42xyz", |i| parse_integer(i), |i| parse_ident(i));
assert!(r.is_some_and(|(n, _)| node_text(&n) == Some("42")));
}
#[test]
fn test_parse_list() {
let (items, _) = parse_list("a, b, c", ",");
assert_eq!(
items,
vec!["a".to_string(), "b".to_string(), "c".to_string()]
);
}
#[test]
fn test_parse_depth() {
assert_eq!(parse_depth("((()))"), 3);
assert_eq!(parse_depth("[]{}"), 1);
assert_eq!(parse_depth("no brackets"), 0);
}
#[test]
fn test_skip_whitespace() {
assert_eq!(skip_whitespace(" hello"), "hello");
}
}