Skip to content

Commit d32c7eb

Browse files
committed
Add parse_tree! macro
1 parent 3c91d76 commit d32c7eb

File tree

4 files changed

+184
-32
lines changed

4 files changed

+184
-32
lines changed

crates/utils/src/parser/macros.rs

Lines changed: 161 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
/// Parse one or more string literals, mapping the results.
1+
/// Macro to define a parser for one or more string literals, mapping the results.
22
///
33
/// This is a replacement for
44
/// [`parser::one_of`](crate::parser::one_of())`(("a".map(|_| Enum::A), "b".map(|_| Enum::b)))`
@@ -55,3 +55,163 @@ macro_rules! parser_literal_map {
5555
$crate::parser::ParseError::ExpectedLiteral($first)
5656
};
5757
}
58+
59+
/// Macro to define a custom parser using a `match` inspired parse tree syntax.
60+
///
61+
/// Each rule is made up of a list of chained parsers enclosed in brackets on the left-hand side.
62+
/// Parsers can be prefixed with an identifier followed by `@` to store the result of that parser in
63+
/// the supplied variable, similar to normal match patterns.
64+
///
65+
/// After the list of parsers, there is an arrow determining the functionality of the rule when the
66+
/// parsers match:
67+
/// - **Expression (`=>`)**: The expression on the right-hand is evaluated and returned.
68+
/// - **Fallible (`=?>`)**: Similar to Expression, but the right-hand side evaluates a result. If
69+
/// the expression evaluates to [`Ok`], the value contained inside is returned. Otherwise,
70+
/// the string contained inside the [`Err`] is handled as a custom
71+
/// [`ParseError`](super::ParseError), and parsing will continue with the following rule.
72+
/// - **Subtree (`=>>`)**: The right-hand side is a nested set of rules enclosed in braces.
73+
///
74+
/// If none of the rules match successfully, the error from the rule which parsed furthest into
75+
/// the input is returned.
76+
///
77+
/// # Examples
78+
/// ```
79+
/// # use utils::parser::{self, Parser};
80+
/// #
81+
/// #[derive(Debug, PartialEq)]
82+
/// enum Register {
83+
/// A, B, C
84+
/// }
85+
///
86+
/// #[derive(Debug, PartialEq)]
87+
/// enum Instruction {
88+
/// Add(Register, Register),
89+
/// AddConstant(Register, i32),
90+
/// Copy(Register, Register),
91+
/// Noop,
92+
/// }
93+
///
94+
/// let register = parser::literal_map!(
95+
/// "A" => Register::A, "B" => Register::B, "C" => Register::C,
96+
/// );
97+
///
98+
/// let instruction = parser::parse_tree!(
99+
/// ("add ", r @ register, ", ") =>> {
100+
/// (r2 @ register) => Instruction::Add(r, r2),
101+
/// (v @ parser::i32()) => Instruction::AddConstant(r, v),
102+
/// },
103+
/// ("copy ", r @ register, ", ", r2 @ register) =?> {
104+
/// if r == r2 {
105+
/// Err("cannot copy register to itself")
106+
/// } else {
107+
/// Ok(Instruction::Copy(r, r2))
108+
/// }
109+
/// },
110+
/// ("noop") => Instruction::Noop,
111+
/// );
112+
///
113+
/// assert_eq!(
114+
/// instruction.parse_complete("add A, B").unwrap(),
115+
/// Instruction::Add(Register::A, Register::B)
116+
/// );
117+
/// assert_eq!(
118+
/// instruction.parse_complete("add C, 100").unwrap(),
119+
/// Instruction::AddConstant(Register::C, 100)
120+
/// );
121+
/// assert_eq!(
122+
/// instruction.parse_complete("copy A, B").unwrap(),
123+
/// Instruction::Copy(Register::A, Register::B)
124+
/// );
125+
/// assert!(instruction
126+
/// .parse_complete("copy A, A")
127+
/// .is_err_and(|err| err.to_string().contains("cannot copy register to itself")));
128+
/// ```
129+
#[macro_export]
130+
macro_rules! parser_parse_tree {
131+
(@rule $input:ident $furthest_err:ident $furthest_remaining:ident [$(,)?] @expr $rhs:expr) => {
132+
return Ok(($rhs, $input));
133+
};
134+
(@rule $input:ident $furthest_err:ident $furthest_remaining:ident [$(,)?] @expr_res $rhs:expr) => {
135+
match $rhs {
136+
Ok(v) => return Ok((v, $input)),
137+
Err(e) => {
138+
if $input.len() < $furthest_remaining {
139+
$furthest_err = $crate::parser::ParseError::Custom(e);
140+
$furthest_remaining = $input.len();
141+
}
142+
}
143+
};
144+
};
145+
(@rule $input:ident $furthest_err:ident $furthest_remaining:ident [$(,)?] @subtree $($rhs:tt)+) => {
146+
$crate::parser_parse_tree!(@toplevel $input $furthest_err $furthest_remaining $($rhs)+);
147+
};
148+
149+
(@rule $input:ident $furthest_err:ident $furthest_remaining:ident
150+
[$n:ident @ $lhs:expr $(,$($tail:tt)*)?] $($rhs:tt)+
151+
) => {
152+
match $crate::parser::Parser::parse(&($lhs), $input) {
153+
Ok(($n, $input)) => {
154+
$crate::parser_parse_tree!(@rule $input $furthest_err $furthest_remaining
155+
[$($($tail)*)?] $($rhs)+
156+
);
157+
},
158+
Err((err, remaining)) => {
159+
if remaining.len() < $furthest_remaining {
160+
$furthest_err = err;
161+
$furthest_remaining = remaining.len();
162+
}
163+
}
164+
};
165+
};
166+
(@rule $input:ident $furthest_err:ident $furthest_remaining:ident
167+
[$lhs:expr $(,$($tail:tt)*)?] $($rhs:tt)+
168+
) => {
169+
match $crate::parser::Parser::parse(&($lhs), $input) {
170+
Ok((_, $input)) => {
171+
$crate::parser_parse_tree!(@rule $input $furthest_err $furthest_remaining
172+
[$($($tail)*)?] $($rhs)+
173+
);
174+
},
175+
Err((err, remaining)) => {
176+
if remaining.len() < $furthest_remaining {
177+
$furthest_err = err;
178+
$furthest_remaining = remaining.len();
179+
}
180+
}
181+
};
182+
};
183+
184+
(@toplevel $input:ident $furthest_err:ident $furthest_remaining:ident
185+
($($lhs:tt)+) => $rhs:expr $(, $($tail:tt)*)?
186+
) => {
187+
$crate::parser_parse_tree!(@rule $input $furthest_err $furthest_remaining [$($lhs)+] @expr $rhs);
188+
$($crate::parser_parse_tree!(@toplevel $input $furthest_err $furthest_remaining $($tail)*);)?
189+
};
190+
(@toplevel $input:ident $furthest_err:ident $furthest_remaining:ident
191+
($($lhs:tt)+) =?> $rhs:expr $(, $($tail:tt)*)?
192+
) => {
193+
$crate::parser_parse_tree!(@rule $input $furthest_err $furthest_remaining [$($lhs)+] @expr_res $rhs);
194+
$($crate::parser_parse_tree!(@toplevel $input $furthest_err $furthest_remaining $($tail)*);)?
195+
};
196+
(@toplevel $input:ident $furthest_err:ident $furthest_remaining:ident
197+
($($lhs:tt)+) =>> {$($rhs:tt)+} $(, $($tail:tt)*)?
198+
) => {
199+
$crate::parser_parse_tree!(@rule $input $furthest_err $furthest_remaining [$($lhs)+] @subtree $($rhs)+);
200+
$($crate::parser_parse_tree!(@toplevel $input $furthest_err $furthest_remaining $($tail)*);)?
201+
};
202+
(@toplevel $input:ident $furthest_err:ident $furthest_remaining:ident $(,)?) => {};
203+
204+
// Ensures this branch only matches inputs starting with (, giving each rule set a unique prefix
205+
(($($first:tt)+) $($tail:tt)+) => {{
206+
fn coerce_to_parser<F: Fn(&[u8]) -> $crate::parser::ParseResult<'_, O>, O>(f: F) -> F { f }
207+
208+
coerce_to_parser(|input| {
209+
let mut furthest_err = $crate::parser::ParseError::Custom("unreachable");
210+
let mut furthest_remaining = usize::MAX;
211+
212+
$crate::parser_parse_tree!(@toplevel input furthest_err furthest_remaining ($($first)+) $($tail)+);
213+
214+
Err((furthest_err, &input[input.len() - furthest_remaining..]))
215+
})
216+
}};
217+
}

crates/utils/src/parser/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,3 +18,4 @@ pub use one_of::one_of;
1818
pub use simple::{byte, byte_range, constant, eof, eol, noop, take_while, take_while1};
1919

2020
pub use crate::parser_literal_map as literal_map;
21+
pub use crate::parser_parse_tree as parse_tree;

crates/year2015/src/day23.rs

Lines changed: 12 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -24,23 +24,20 @@ enum Instruction {
2424

2525
impl Day23 {
2626
pub fn new(input: &str, _: InputType) -> Result<Self, InputError> {
27-
let register = parser::literal_map!("a" => Register::A, "b" => Register::B);
27+
let register = parser::literal_map!(
28+
"a" => Register::A,
29+
"b" => Register::B,
30+
);
2831

2932
Ok(Self {
30-
instructions: parser::one_of((
31-
register.with_prefix("hlf ").map(Instruction::Half),
32-
register.with_prefix("tpl ").map(Instruction::Triple),
33-
register.with_prefix("inc ").map(Instruction::Increment),
34-
parser::i16().with_prefix("jmp ").map(Instruction::Jump),
35-
register
36-
.with_prefix("jie ")
37-
.then(parser::i16().with_prefix(", "))
38-
.map(|(r, o)| Instruction::JumpIfEven(r, o)),
39-
register
40-
.with_prefix("jio ")
41-
.then(parser::i16().with_prefix(", "))
42-
.map(|(r, o)| Instruction::JumpIfOne(r, o)),
43-
))
33+
instructions: parser::parse_tree!(
34+
("hlf ", r @ register) => Instruction::Half(r),
35+
("tpl ", r @ register) => Instruction::Triple(r),
36+
("inc ", r @ register) => Instruction::Increment(r),
37+
("jmp ", v @ parser::i16()) => Instruction::Jump(v),
38+
("jie ", r @ register, ", ", o @ parser::i16()) => Instruction::JumpIfEven(r, o),
39+
("jio ", r @ register, ", ", o @ parser::i16()) => Instruction::JumpIfOne(r, o),
40+
)
4441
.parse_lines(input)?,
4542
})
4643
}

crates/year2016/src/assembunny.rs

Lines changed: 10 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -48,32 +48,26 @@ impl<const TGL: bool, const OUT: bool> Interpreter<TGL, OUT> {
4848
.or(parser::i32().map(Value::Number));
4949

5050
Ok(Self {
51-
instructions: parser::one_of((
52-
register.with_prefix("inc ").map(Instruction::Increment),
53-
register.with_prefix("dec ").map(Instruction::Decrement),
54-
value
55-
.with_prefix("cpy ")
56-
.then(register.with_prefix(" "))
57-
.map(|(v, r)| Instruction::Copy(v, r)),
58-
value
59-
.with_prefix("jnz ")
60-
.then(value.with_prefix(" "))
61-
.map(|(v, o)| Instruction::JumpIfNotZero(v, o)),
62-
register.with_prefix("tgl ").map_res(|r| {
51+
instructions: parser::parse_tree!(
52+
("inc ", r @ register) => Instruction::Increment(r),
53+
("dec ", r @ register) => Instruction::Decrement(r),
54+
("cpy ", v @ value, " ", r @ register) => Instruction::Copy(v, r),
55+
("jnz ", v @ value, " ", o @ value) => Instruction::JumpIfNotZero(v, o),
56+
("tgl ", r @ register) =?> {
6357
if TGL {
6458
Ok(Instruction::Toggle(r))
6559
} else {
6660
Err("tgl instruction not supported")
6761
}
68-
}),
69-
register.with_prefix("out ").map_res(|r| {
62+
},
63+
("out ", r @ register) =?> {
7064
if OUT {
7165
Ok(Instruction::Out(r))
7266
} else {
7367
Err("out instruction not supported")
7468
}
75-
}),
76-
))
69+
},
70+
)
7771
.parse_lines(input)?,
7872
})
7973
}

0 commit comments

Comments
 (0)