Day 18
Feelings
Weeeell. There's no algorith, just go and parse.
Pest doesn't suck as much as I thought on day 1. Especially for a task like this it's pretty damn good.
Learnings
Learned to use pest parser a little better.
Unfortunately the first sample in A thoughtful introduction to the pest parser happens to almost completely cover this day.
Parser
In pest syntax (see Pest Grammar):
num = @{ ASCII_DIGIT+ }
operation = _{ add | multiply }
add = { "+" }
multiply = { "*" }
expr = { term ~ (operation ~ term)* }
term = _{ num | "(" ~ expr ~ ")" }
calculation = _{ SOI ~ expr ~ EOI }
WHITESPACE = _{ " "+ }
As an example, 2*3+(4 * 5) can be parsed using:
Calculation::parse(Rule::calculation, "2*3+(4 * 5)").unwrap()
and it results to returning Pair structs
[
Pair {
rule: expr,
span: Span {
str: "2*3+(4 * 5)",
start: 0,
end: 11,
},
inner: [
Pair {
rule: num,
span: Span {
str: "2",
start: 0,
end: 1,
},
inner: [],
},
Pair {
rule: multiply,
span: Span {
str: "*",
start: 1,
end: 2,
},
inner: [],
},
Pair {
rule: num,
span: Span {
str: "3",
start: 2,
end: 3,
},
inner: [],
},
Pair {
rule: add,
span: Span {
str: "+",
start: 3,
end: 4,
},
inner: [],
},
Pair {
rule: expr,
span: Span {
str: "4 * 5",
start: 5,
end: 10,
},
inner: [
Pair {
rule: num,
span: Span {
str: "4",
start: 5,
end: 6,
},
inner: [],
},
Pair {
rule: multiply,
span: Span {
str: "*",
start: 7,
end: 8,
},
inner: [],
},
Pair {
rule: num,
span: Span {
str: "5",
start: 9,
end: 10,
},
inner: [],
},
],
},
],
},
Pair {
rule: EOI,
span: Span {
str: "",
start: 11,
end: 11,
},
inner: [],
},
]
Or more compact, evaluated by pest editor
- expr
- num: "2"
- multiply: "*"
- num: "3"
- add: "+"
- expr
- num: "4"
- multiply: "*"
- num: "5"
- EOI: ""
This can be parsed using:
fn eval(expression: Pairs<Rule>) -> i64 {
PREC_CLIMBER.climb(
expression,
|pair: Pair<Rule>| match pair.as_rule() {
Rule::num => pair.as_str().parse::<i64>().unwrap(),
Rule::expr => eval(pair.into_inner()),
_ => unreachable!(),
},
|lhs: i64, op: Pair<Rule>, rhs: i64| match op.as_rule() {
Rule::add => lhs + rhs,
Rule::multiply => lhs * rhs,
_ => unreachable!(),
},
)
}
That in turn uses PrecClimber:
lazy_static! {
static ref PREC_CLIMBER: PrecClimber<Rule> = {
use Rule::*;
use Assoc::*;
PrecClimber::new(vec![
Operator::new(multiply, Left),
Operator::new(add, Left),
])
};
}
Then the difference between part 1 and part 2 becomes just defining the evaluation order.
Part1:
PrecClimber::new(vec![
Operator::new(multiply, Left)|
Operator::new(add, Left),
])
Part2:
PrecClimber::new(vec![
Operator::new(multiply, Left),
Operator::new(add, Left),
])
Which is more or less one char difference between part1 and part2.