Last active
December 16, 2022 14:57
-
-
Save AnthonyMikh/d582ff0ba1cde2987ab107ec0b05772d to your computer and use it in GitHub Desktop.
Crude parsing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#[derive(Clone)] | |
struct Lexer<'a> { | |
s: &'a str, | |
} | |
impl<'a> Lexer<'a> { | |
fn of(s: &'a str) -> Self { | |
Self { s } | |
} | |
fn shift(&mut self, pos: usize) { | |
self.s = &self.s[pos..]; | |
} | |
fn end(&mut self) -> Option<()> { | |
if self.s.is_empty() { | |
Some(()) | |
} else { | |
None | |
} | |
} | |
fn literal(&mut self, literal: &str) -> Option<()> { | |
self.s = self.s.strip_prefix(literal)?; | |
Some(()) | |
} | |
fn before_literal(&mut self, literal: &str) -> Option<&'a str> { | |
let pos = self.s.find(literal)?; | |
let ret = &self.s[..pos]; | |
self.shift(pos + literal.len()); | |
Some(ret) | |
} | |
fn number<Num: std::str::FromStr>(&mut self) -> Option<Num> { | |
let pos = self | |
.s | |
.as_bytes() | |
.iter() | |
.position(|ch| !ch.is_ascii_digit()) | |
.unwrap_or(self.s.len()); | |
let ret = self.s[..pos].parse().ok()?; | |
self.shift(pos); | |
Some(ret) | |
} | |
fn signed_number<Num: std::str::FromStr>(&mut self) -> Option<Num> { | |
let offset = self.s.starts_with("-") as usize; | |
let offsetted = &self.s[offset..]; | |
let pos = offsetted | |
.as_bytes() | |
.iter() | |
.position(|ch| !ch.is_ascii_digit()) | |
.unwrap_or(offsetted.len()) + offset; | |
let ret = self.s[..pos].parse().ok()?; | |
self.shift(pos); | |
Some(ret) | |
} | |
fn optional<T, F: FnOnce(&mut Self) -> Option<T>>(&mut self, f: F) -> Option<T> { | |
let backtrack = self.clone(); | |
let ret = f(self); | |
if ret.is_none() { | |
*self = backtrack; | |
} | |
ret | |
} | |
} | |
fn parse_bags_with_amount(line: &str) -> Option<(&str, Vec<(&str, usize)>)> { | |
let mut p = Lexer::of(line); | |
let outer_color = p.before_literal(" bags contain ")?; | |
if p.optional(|p| { | |
p.literal("no other bags.")?; | |
p.end() | |
}) | |
.is_some() | |
{ | |
return Some((outer_color, Vec::new())); | |
} | |
let mut inner_colors = Vec::new(); | |
loop { | |
let amount = p.number()?; | |
let inner_color = p.before_literal(" bag")?.trim_start_matches(' '); | |
p.optional(|p| p.literal("s")); | |
inner_colors.push((inner_color, amount)); | |
if p.optional(|p| p.literal(", ")).is_none() { | |
break; | |
} | |
} | |
p.literal(".")?; | |
p.end()?; | |
Some((outer_color, inner_colors)) | |
} | |
fn main() { | |
let input = "\ | |
bright white bags contain 1 shiny gold bag. | |
vibrant plum bags contain 5 faded blue bags, 6 dotted black bags. | |
faded blue bags contain no other bags."; | |
let parsed = input | |
.lines() | |
.map(parse_bags_with_amount) | |
.map(Option::unwrap) | |
.collect::<Vec<_>>(); | |
assert_eq!( | |
parsed, | |
[ | |
("bright white", vec![("shiny gold", 1)]), | |
("vibrant plum", vec![("faded blue", 5), ("dotted black", 6)]), | |
("faded blue", vec![]), | |
], | |
); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment