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.