Skip to content

Commit cd53504

Browse files
committed
Add byte_map! parser macro
1 parent 32f6c10 commit cd53504

File tree

5 files changed

+134
-22
lines changed

5 files changed

+134
-22
lines changed

crates/utils/src/parser/macros.rs

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,77 @@
1+
/// Macro to define a parser which consumes a single byte and maps it using a lookup table.
2+
///
3+
/// This macro is a wrapper around [`parser::byte_lut`](crate::parser::byte_lut) to allow defining
4+
/// the lookup table using a match-like syntax. Each expression must be const and evaluate to a
5+
/// value of the same copy type.
6+
///
7+
/// # Examples
8+
/// ```
9+
/// # use utils::parser::{Parser, self};
10+
/// let parser = parser::byte_map!(
11+
/// b'#' => true,
12+
/// b'.' | b'S' => false,
13+
/// );
14+
/// assert_eq!(parser.parse(b"#.S##"), Ok((true, &b".S##"[..])));
15+
/// assert_eq!(parser.parse(b".S##"), Ok((false, &b"S##"[..])));
16+
/// assert_eq!(parser.parse(b"S##"), Ok((false, &b"##"[..])));
17+
///
18+
/// let (err, remaining) = parser.parse(b"abc").unwrap_err();
19+
/// assert_eq!(err.to_string(), "expected one of '#', '.', 'S'");
20+
/// assert_eq!(remaining, &b"abc"[..]);
21+
/// ```
22+
#[macro_export]
23+
macro_rules! parser_byte_map {
24+
(
25+
$($($l:literal)|+ => $e:expr),+$(,)?
26+
) => {{
27+
// `let _: u8 = $l` ensures $l is used in the repetition and also ensures all the literals
28+
// are byte literals
29+
const COUNT: usize = 0usize $($(+ {let _: u8 = $l; 1usize})+)+;
30+
const LEN: usize = 14 + 5 * COUNT;
31+
const {
32+
assert!(COUNT >= 2, "at least two literals must be provided");
33+
}
34+
35+
// Once concat_bytes! is stabilized this error message can be created in the macro similar
36+
// to parser_literal_map!
37+
const ERROR: [u8; LEN] = {
38+
let mut result = [0u8; LEN];
39+
let (prefix, vals) = result.split_at_mut(16);
40+
prefix.copy_from_slice(b"expected one of ");
41+
42+
let mut i = 0;
43+
let literals = [$($($l),+),+];
44+
while i < COUNT {
45+
vals[i * 5] = b'\'';
46+
vals[i * 5 + 1] = literals[i];
47+
vals[i * 5 + 2] = b'\'';
48+
if i + 1 < COUNT {
49+
vals[i * 5 + 3] = b',';
50+
vals[i * 5 + 4] = b' ';
51+
}
52+
i += 1;
53+
}
54+
55+
result
56+
};
57+
58+
$crate::parser::byte_lut(&const {
59+
// Don't use a const item for the lut to avoid naming the value type
60+
let mut lut = [None; 256];
61+
$($(
62+
assert!(lut[$l as usize].is_none(), "duplicate literal");
63+
lut[$l as usize] = Some($e);
64+
)+)+
65+
lut
66+
}, const {
67+
match str::from_utf8(&ERROR) {
68+
Ok(v) => v,
69+
Err(_) => panic!("one or more of the provided literals is invalid unicode"),
70+
}
71+
})
72+
}};
73+
}
74+
175
/// Macro to define a parser for one or more string literals, mapping the results.
276
///
377
/// This is a replacement for

crates/utils/src/parser/mod.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,9 @@ pub use error::ParseError;
1515
pub use iterator::{ParserIterator, ParserMatchesIterator};
1616
pub use number::{i8, i16, i32, i64, i128, number_range, u8, u16, u32, u64, u128};
1717
pub use one_of::one_of;
18-
pub use simple::{byte, byte_range, constant, eof, eol, noop, take_while, take_while1};
18+
pub use simple::{byte, byte_lut, byte_range, constant, eof, eol, noop, take_while, take_while1};
1919

20+
pub use crate::parser_byte_map as byte_map;
2021
pub use crate::parser_literal_map as literal_map;
2122
pub use crate::parser_parsable_enum as parsable_enum;
2223
pub use crate::parser_parse_tree as parse_tree;

crates/utils/src/parser/simple.rs

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,56 @@ pub fn byte() -> Byte {
3939
Byte()
4040
}
4141

42+
#[derive(Copy, Clone)]
43+
pub struct ByteLut<'a, O> {
44+
lut: &'a [Option<O>; 256],
45+
error: &'static str,
46+
}
47+
impl<'i, O: Copy> Parser<'i> for ByteLut<'_, O> {
48+
type Output = O;
49+
type Then<T: Parser<'i>> = Then2<Self, T>;
50+
51+
#[inline]
52+
fn parse(&self, input: &'i [u8]) -> ParseResult<'i, Self::Output> {
53+
if let [byte, remaining @ ..] = input
54+
&& let Some(output) = self.lut[*byte as usize]
55+
{
56+
Ok((output, remaining))
57+
} else {
58+
Err((ParseError::Custom(self.error), input))
59+
}
60+
}
61+
}
62+
63+
/// Parser that consumes a single byte and maps it using a lookup table.
64+
///
65+
/// Equivalent to `parser::byte().map_res(|b| LOOKUP[b as usize].ok_or("expected ..."))`, which is
66+
/// usually faster than an equivalent match statement in the closure.
67+
///
68+
/// See also [`parser::byte_map!`](crate::parser::byte_map!) which wraps this function, allowing a
69+
/// match-like syntax to be used to define the lookup table.
70+
///
71+
/// # Examples
72+
/// ```
73+
/// # use utils::parser::{self, Parser, ParseError};
74+
/// const LOOKUP: [Option<bool>; 256] = {
75+
/// let mut x = [None; 256];
76+
/// x['#' as usize] = Some(true);
77+
/// x['.' as usize] = Some(false);
78+
/// x
79+
/// };
80+
///
81+
/// let parser = parser::byte_lut(&LOOKUP, "expected '#' or '.'");
82+
/// assert_eq!(parser.parse(b"#..##"), Ok((true, &b"..##"[..])));
83+
/// assert_eq!(parser.parse(b"..##"), Ok((false, &b".##"[..])));
84+
/// assert_eq!(parser.parse(b"abc"), Err((ParseError::Custom("expected '#' or '.'"), &b"abc"[..])));
85+
/// ```
86+
#[inline]
87+
#[must_use]
88+
pub fn byte_lut<'a, T: Copy>(lut: &'a [Option<T>; 256], error: &'static str) -> ByteLut<'a, T> {
89+
ByteLut { lut, error }
90+
}
91+
4292
#[derive(Copy, Clone)]
4393
pub struct ByteRange {
4494
min: u8,

crates/year2024/src/day19.rs

Lines changed: 7 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -24,23 +24,18 @@ struct TrieNode {
2424
is_terminal: bool,
2525
}
2626

27-
const LETTER_LOOKUP: [Option<Stripe>; 256] = {
28-
let mut x = [None; 256];
29-
x[b'w' as usize] = Some(Stripe::W);
30-
x[b'u' as usize] = Some(Stripe::U);
31-
x[b'b' as usize] = Some(Stripe::B);
32-
x[b'r' as usize] = Some(Stripe::R);
33-
x[b'g' as usize] = Some(Stripe::G);
34-
x
35-
};
36-
3727
const MAX_PATTERN_LENGTH: usize = 8;
3828
const MAX_DESIGN_LENGTH: usize = 64;
3929

4030
impl Day19 {
4131
pub fn new(input: &str, _: InputType) -> Result<Self, InputError> {
42-
let letter = parser::byte()
43-
.map_res(|b| LETTER_LOOKUP[b as usize].ok_or("expected 'w', 'u', 'b', 'r' or 'g'"));
32+
let letter = parser::byte_map!(
33+
b'w' => Stripe::W,
34+
b'u' => Stripe::U,
35+
b'b' => Stripe::B,
36+
b'r' => Stripe::R,
37+
b'g' => Stripe::G,
38+
);
4439

4540
let Some((patterns, designs)) = input.split_once("\n\n") else {
4641
return Err(InputError::new(input, 0, "expected patterns and designs"));

crates/year2024/src/day25.rs

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,20 +7,12 @@ pub struct Day25 {
77
keys: Vec<u32>,
88
}
99

10-
const LOOKUP: [Option<bool>; 256] = {
11-
let mut x = [None; 256];
12-
x['#' as usize] = Some(true);
13-
x['.' as usize] = Some(false);
14-
x
15-
};
16-
1710
impl Day25 {
1811
pub fn new(input: &str, _: InputType) -> Result<Self, InputError> {
1912
let mut locks = Vec::with_capacity(250);
2013
let mut keys = Vec::with_capacity(250);
2114

22-
for item in parser::byte()
23-
.map_res(|b| LOOKUP[b as usize].ok_or("expected '.' or '#'"))
15+
for item in parser::byte_map!(b'#' => true, b'.' => false)
2416
.repeat_n::<5, _>(parser::noop())
2517
.repeat_n::<7, _>(parser::eol())
2618
.with_consumed()

0 commit comments

Comments
 (0)