aoc/year2015/
day12.rs

1//! # JSAbacusFramework.io
2//!
3//! ## Part One
4//!
5//! The utility [`iter_signed`] method extracts numbers from surrounding text and is used directly.
6//!
7//! ## Part Two
8//!
9//! We build a tiny custom JSON parser using a
10//! [parser combinator](https://en.wikipedia.org/wiki/Parser_combinator) approach,
11//! making some simplifying assumptions:
12//! * The input is always well formed and does not contain any whitespace.
13//! * Arrays and objects contain at least one item.
14//! * We don't care about the content of strings, only if they equal "red" or not.
15//!
16//! Each parsing function returns a [`Result`] struct which has 3 fields:
17//! * `next` The index of the character *after* this object. For example parsing "123," returns
18//!   a value of 3 for next.
19//! * `ignore`: Only true for strings that exactly equals "red", false otherwise and always
20//!   false for numbers, arrays and objects.
21//! * `value`: For numbers the literal value, for string zero, for arrays the sum of child
22//!   items, for objects the sum of child items if no "red" property is present, otherwise zero.
23//!
24//! [`iter_signed`]: crate::util::parse
25use crate::util::parse::*;
26
27const RED: &[u8] = b"red";
28
29struct Result {
30    next: usize,
31    ignore: bool,
32    value: i32,
33}
34
35pub fn parse(input: &str) -> &str {
36    input
37}
38
39pub fn part1(input: &str) -> i32 {
40    input.iter_signed::<i32>().sum()
41}
42
43pub fn part2(input: &str) -> i32 {
44    parse_json(input.as_bytes(), 0).value
45}
46
47/// Parse JSON that has no whitespace.
48fn parse_json(input: &[u8], start: usize) -> Result {
49    match input[start] {
50        b'[' => parse_array(input, start),
51        b'{' => parse_object(input, start),
52        b'"' => parse_string(input, start),
53        _ => parse_number(input, start),
54    }
55}
56
57/// Parse array assuming it contains at least one element.
58fn parse_array(input: &[u8], start: usize) -> Result {
59    let mut index = start;
60    let mut total = 0;
61
62    while input[index] != b']' {
63        let Result { next, value, .. } = parse_json(input, index + 1);
64        index = next;
65        total += value;
66    }
67
68    Result { next: index + 1, ignore: false, value: total }
69}
70
71/// Parse object assuming it contains at least one key/value pair.
72fn parse_object(input: &[u8], start: usize) -> Result {
73    let mut index = start;
74    let mut total = 0;
75    let mut ignore = false;
76
77    while input[index] != b'}' {
78        let Result { next: first, .. } = parse_json(input, index + 1);
79        let Result { next: second, ignore: red, value } = parse_json(input, first + 1);
80        index = second;
81        total += value;
82        ignore |= red;
83    }
84
85    Result { next: index + 1, ignore: false, value: if ignore { 0 } else { total } }
86}
87
88/// Parse a string evaluating only if it equals "red".
89fn parse_string(input: &[u8], start: usize) -> Result {
90    let start = start + 1;
91    let mut end = start;
92
93    while input[end] != b'"' {
94        end += 1;
95    }
96
97    Result { next: end + 1, ignore: &input[start..end] == RED, value: 0 }
98}
99
100/// Parse an integer value.
101fn parse_number(input: &[u8], start: usize) -> Result {
102    let mut end = start;
103    let mut neg = false;
104    let mut acc = 0;
105
106    if input[end] == b'-' {
107        neg = true;
108        end += 1;
109    }
110
111    while input[end].is_ascii_digit() {
112        acc = 10 * acc + (input[end] - b'0') as i32;
113        end += 1;
114    }
115
116    Result { next: end, ignore: false, value: if neg { -acc } else { acc } }
117}