296 lines
5.8 KiB
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
|
|
}
|