<- Back to shtanton's homepage
aboutsummaryrefslogtreecommitdiff
path: root/json_array/write.go
blob: 4d202c4ac1059c75e8d91a8b403c2d7fc2e07721 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
package json_array

import (
	"bufio"
	"strings"
	"main/walk"
	"encoding/json"
)

func assembleValue(atoms []walk.Atom) (interface{}, []walk.Atom) {
	if len(atoms) == 0 {
		panic("Missing JSON value in output")
	}
	switch atoms[0].Typ {
		case walk.AtomNull:
			return nil, atoms[1:]
		case walk.AtomBool:
			return atoms[0].Bool(), atoms[1:]
		case walk.AtomNumber:
			return atoms[0].Number(), atoms[1:]
		case walk.AtomStringTerminal:
			var builder strings.Builder
			atoms = atoms[1:]
			for {
				if len(atoms) == 0 {
					panic("Missing closing string terminal")
				}
				if atoms[0].Typ == walk.AtomStringTerminal {
					break
				}
				if atoms[0].Typ != walk.AtomStringRune {
					panic("Non string rune atom inside string")
				}
				builder.WriteRune(atoms[0].StringRune())
				atoms = atoms[1:]
			}
			atoms = atoms[1:]
			return builder.String(), atoms
		case walk.AtomStringRune:
			panic("String rune used outside of string terminals")
		case walk.AtomTerminal:
			terminal := atoms[0].Terminal()
			switch terminal {
				case walk.ArrayEnd, walk.MapEnd:
					panic("Tried to extract value from end terminal")
				case walk.ArrayBegin:
					var arr []interface{}
					var element interface{}
					atoms = atoms[1:]
					for {
						if len(atoms) == 0 {
							panic("Missing array end terminal")
						}
						if atoms[0].Typ == walk.AtomTerminal && atoms[0].Terminal() == walk.ArrayEnd {
							atoms = atoms[1:]
							break
						}
						element, atoms = assembleValue(atoms)
						arr = append(arr, element)
					}
					return arr, atoms
				case walk.MapBegin:
					obj := make(map[string]interface{})
					var key interface{}
					var element interface{}
					atoms = atoms[1:]
					for {
						if len(atoms) == 0 {
							panic("Missing map end terminal")
						}
						if atoms[0].Typ == walk.AtomTerminal && atoms[0].Terminal() == walk.MapEnd {
							atoms = atoms[1:]
							break
						}
						key, atoms = assembleValue(atoms)
						element, atoms = assembleValue(atoms)
						keyString, keyIsString := key.(string)
						if !keyIsString {
							panic("Key is not string")
						}
						obj[keyString] = element
					}
					return obj, atoms
				default:
					panic("Invalid terminal")
			}
		default:
			panic("Invalid atom")
	}
}

func outputValue(atoms []walk.Atom, writer *bufio.Writer) {
	if len(atoms) == 0 {
		return
	}
	value, atoms := assembleValue(atoms)
	if len(atoms) != 0 {
		panic("Tried to output more than one JSON value")
	}
	bytes, err := json.MarshalIndent(value, "\t", "\t")
	if err != nil {
		panic("Error marshalling json into bytes")
	}
	_, err = writer.Write(bytes)
	if err != nil {
		panic("Error writing value")
	}
}

type writerState int
const (
	writerStateStart writerState = iota
	writerStateValue
)

func NewJSONArrayWriter(writer *bufio.Writer) *JSONArrayWriter {
	return &JSONArrayWriter {
		writer: writer,
		state: writerStateStart,
	}
}

type JSONArrayWriter struct {
	writer *bufio.Writer
	state writerState
}

func (out *JSONArrayWriter) Write(item walk.WalkItem) error {
	switch out.state {
		case writerStateStart:
			_, err := out.writer.WriteString("[\n\t")
			if err != nil {
				panic("Error outputting [ at beginning of array")
			}
			outputValue(item.Value, out.writer)
			out.state = writerStateValue
			return nil
		case writerStateValue:
			_, err := out.writer.WriteString(",\n\t")
			if err != nil {
				panic("Error outputting comma at the end of a value")
			}
			outputValue(item.Value, out.writer)
			return nil
		default:
			panic("Invalid writer state")
	}
}

func (out *JSONArrayWriter) AssertDone() {
	if out.state == writerStateStart {
		out.writer.WriteString("[")
	}
	out.writer.WriteString("\n]")
	out.writer.Flush()
}