advent-of-code/2021/18/pairs/parser.go

296 lines
5.8 KiB
Go

package pairs
import (
"bufio"
"bytes"
"fmt"
"io"
"strconv"
)
// Token represents a lexical token.
type Token int
const (
// Special tokens
ILLEGAL Token = iota
EOF
WHITESPACE
// Literals
INT // integers
// Misc characters
COMMA // ,
OPEN_BRACKET
CLOSE_BRACKET
)
type Pair struct {
HasSubPairs bool
Value int
Left, Right *Pair
}
func (p Pair) String() string {
if p.HasSubPairs {
return fmt.Sprintf("[%s, %s]", p.Left.String(), p.Right.String())
}
return fmt.Sprintf("%d", p.Value)
}
func (p Pair) Magnitude() int {
if p.HasSubPairs {
return 3*p.Left.Magnitude() + 2*p.Right.Magnitude()
}
return p.Value
}
func (p1 *Pair) Add(p2 *Pair) *Pair {
p := &Pair{
HasSubPairs: true,
Left: p1,
Right: p2,
}
p.Reduce()
return p
}
func (p Pair) DeepCopy() *Pair {
if p.HasSubPairs {
return &Pair{
HasSubPairs: true,
Left: p.Left.DeepCopy(),
Right: p.Right.DeepCopy(),
}
}
return &Pair{Value: p.Value}
}
func (p *Pair) Reduce() {
for {
if exploded, _, _ := p.TryExplode(0); exploded {
continue
}
if p.TrySplit() {
continue
}
break
}
}
func (p *Pair) TrySplit() bool {
if p.HasSubPairs {
if p.Left.TrySplit() {
return true
}
return p.Right.TrySplit()
}
if p.Value >= 10 {
p.HasSubPairs = true
p.Left = &Pair{Value: p.Value / 2}
p.Right = &Pair{Value: p.Value/2 + p.Value%2}
p.Value = 0
return true
}
return false
}
func (p *Pair) TryExplode(depth int) (bool, *int, *int) {
if !p.HasSubPairs {
return false, nil, nil
}
if depth >= 4 {
p.HasSubPairs = false
p.Value = 0
l := p.Left.Value
r := p.Right.Value
p.Left = nil
p.Right = nil
return true, &l, &r
}
depth++
if exploded, l, r := p.Left.TryExplode(depth); exploded {
if r != nil {
r = p.Right.TryAddToLeftMostNumber(r)
}
return exploded, l, r
}
if exploded, l, r := p.Right.TryExplode(depth); exploded {
if l != nil {
l = p.Left.TryAddToRightMostNumber(l)
}
return exploded, l, r
}
return false, nil, nil
}
func (p *Pair) TryAddToLeftMostNumber(n *int) *int {
if p.HasSubPairs {
return p.Left.TryAddToLeftMostNumber(n)
}
p.Value += *n
return nil
}
func (p *Pair) TryAddToRightMostNumber(n *int) *int {
if p.HasSubPairs {
return p.Right.TryAddToRightMostNumber(n)
}
p.Value += *n
return nil
}
func isWhitespace(ch rune) bool {
return ch == ' ' || ch == '\t' || ch == '\n'
}
func isDigit(ch rune) bool {
return (ch >= '0' && ch <= '9')
}
var eof = rune(0)
// Scanner represents a lexical scanner.
type Scanner struct {
r *bufio.Reader
}
// NewScanner returns a new instance of Scanner.
func NewScanner(r io.Reader) *Scanner {
return &Scanner{r: bufio.NewReader(r)}
}
// read reads the next rune from the bufferred reader.
// Returns the rune(0) if an error occurs (or io.EOF is returned).
func (s *Scanner) read() rune {
ch, _, err := s.r.ReadRune()
if err != nil {
return eof
}
return ch
}
// unread places the previously read rune back on the reader.
func (s *Scanner) unread() { _ = s.r.UnreadRune() }
// Scan returns the next token and literal value.
func (s *Scanner) Scan() (tok Token, lit string) {
// Read the next rune.
ch := s.read()
// If we see whitespace then consume all contiguous whitespace.
for isWhitespace(ch) {
ch = s.read()
}
// If we see a digit then consume as an int.
if isDigit(ch) {
return s.scanInt(ch)
}
// Otherwise read the individual character.
switch ch {
case eof:
return EOF, ""
case ',':
return COMMA, string(ch)
case '[':
return OPEN_BRACKET, string(ch)
case ']':
return CLOSE_BRACKET, string(ch)
}
return ILLEGAL, string(ch)
}
// scanInt consumes the current rune and all contiguous digit runes.
func (s *Scanner) scanInt(read rune) (tok Token, lit string) {
// Create a buffer and read the current character into it.
var buf bytes.Buffer
buf.WriteRune(read)
// Read every subsequent ident character into the buffer.
// Non-ident characters and EOF will cause the loop to exit.
for {
if ch := s.read(); ch == eof {
break
} else if !isDigit(ch) {
s.unread()
break
} else {
_, _ = buf.WriteRune(ch)
}
}
// Otherwise return as a regular identifier.
return INT, buf.String()
}
// Parser represents a parser.
type Parser struct {
s *Scanner
buf struct {
tok Token // last read token
lit string // last read literal
n int // buffer size (max=1)
}
}
// NewParser returns a new instance of Parser.
func NewParser(r io.Reader) *Parser {
return &Parser{s: NewScanner(r)}
}
// scan returns the next token from the underlying scanner.
// If a token has been unscanned then read that instead.
func (p *Parser) scan() (tok Token, lit string) {
// If we have a token on the buffer, then return it.
if p.buf.n != 0 {
p.buf.n = 0
return p.buf.tok, p.buf.lit
}
// Otherwise read the next token from the scanner.
tok, lit = p.s.Scan()
// Save it to the buffer in case we unscan later.
p.buf.tok, p.buf.lit = tok, lit
return
}
// unscan pushes the previously read token back onto the buffer.
func (p *Parser) unscan() { p.buf.n = 1 }
func (p *Parser) Parse() (pair *Pair, err error) {
pair = &Pair{}
tok, lit := p.scan()
switch tok {
case OPEN_BRACKET:
pair.HasSubPairs = true
pair.Left, err = p.Parse()
if err != nil {
return nil, err
}
if tok, lit := p.scan(); tok != COMMA {
return nil, fmt.Errorf("found %q, expected COMMA", lit)
}
pair.Right, err = p.Parse()
if err != nil {
return nil, err
}
if tok, lit := p.scan(); tok != CLOSE_BRACKET {
return nil, fmt.Errorf("found %q, expected CLOSE_BRACKET", lit)
}
case INT:
if v, err := strconv.Atoi(lit); err != nil {
return nil, fmt.Errorf("failed to atoi an INT %s : %w", lit, err)
} else {
pair.Value = v
}
default:
return nil, fmt.Errorf("unexpected %q, expected OPEN_BRACKER or INT", lit)
}
return pair, nil
}