1package main
2
3import (
4	"encoding/json"
5	"fmt"
6	"io"
7	"reflect"
8	"strconv"
9	"strings"
10
11	"github.com/pkg/errors"
12)
13
14// A statement is a slice of tokens representing an assignment statement.
15// An assignment statement is something like:
16//
17//   json.city = "Leeds";
18//
19// Where 'json', '.', 'city', '=', '"Leeds"' and ';' are discrete tokens.
20// Statements are stored as tokens to make sorting more efficient, and so
21// that the same type can easily be used when gronning and ungronning.
22type statement []token
23
24// String returns the string form of a statement rather than the
25// underlying slice of tokens
26func (s statement) String() string {
27	out := make([]string, 0, len(s)+2)
28	for _, t := range s {
29		out = append(out, t.format())
30	}
31	return strings.Join(out, "")
32}
33
34// colorString returns the string form of a statement with ASCII color codes
35func (s statement) colorString() string {
36	out := make([]string, 0, len(s)+2)
37	for _, t := range s {
38		out = append(out, t.formatColor())
39	}
40	return strings.Join(out, "")
41}
42
43// a statementconv converts a statement to string
44type statementconv func(s statement) string
45
46// statementconv variant of statement.String
47func statementToString(s statement) string {
48	return s.String()
49}
50
51// statementconv variant of statement.colorString
52func statementToColorString(s statement) string {
53	return s.colorString()
54}
55
56// withBare returns a copy of a statement with a new bare
57// word token appended to it
58func (s statement) withBare(k string) statement {
59	new := make(statement, len(s), len(s)+2)
60	copy(new, s)
61	return append(
62		new,
63		token{".", typDot},
64		token{k, typBare},
65	)
66}
67
68// jsonify converts an assignment statement to a JSON representation
69func (s statement) jsonify() (statement, error) {
70	// If m is the number of keys occurring in the left hand side
71	// of s, then len(s) is in between 2*m+4 and 3*m+4. The resultant
72	// statement j (carrying the JSON representation) is always 2*m+5
73	// long. So len(s)+1 ≥ 2*m+5 = len(j). Therefore an initaial
74	// allocation of j with capacity len(s)+1 will allow us to carry
75	// through without reallocation.
76	j := make(statement, 0, len(s)+1)
77	if len(s) < 4 || s[0].typ != typBare || s[len(s)-3].typ != typEquals ||
78		s[len(s)-1].typ != typSemi {
79		return nil, errors.New("non-assignment statement")
80	}
81
82	j = append(j, token{"[", typLBrace})
83	j = append(j, token{"[", typLBrace})
84	for _, t := range s[1 : len(s)-3] {
85		switch t.typ {
86		case typNumericKey, typQuotedKey:
87			j = append(j, t)
88			j = append(j, token{",", typComma})
89		case typBare:
90			j = append(j, token{quoteString(t.text), typQuotedKey})
91			j = append(j, token{",", typComma})
92		}
93	}
94	if j[len(j)-1].typ == typComma {
95		j = j[:len(j)-1]
96	}
97	j = append(j, token{"]", typLBrace})
98	j = append(j, token{",", typComma})
99	j = append(j, s[len(s)-2])
100	j = append(j, token{"]", typLBrace})
101
102	return j, nil
103}
104
105// withQuotedKey returns a copy of a statement with a new
106// quoted key token appended to it
107func (s statement) withQuotedKey(k string) statement {
108	new := make(statement, len(s), len(s)+3)
109	copy(new, s)
110	return append(
111		new,
112		token{"[", typLBrace},
113		token{quoteString(k), typQuotedKey},
114		token{"]", typRBrace},
115	)
116}
117
118// withNumericKey returns a copy of a statement with a new
119// numeric key token appended to it
120func (s statement) withNumericKey(k int) statement {
121	new := make(statement, len(s), len(s)+3)
122	copy(new, s)
123	return append(
124		new,
125		token{"[", typLBrace},
126		token{strconv.Itoa(k), typNumericKey},
127		token{"]", typRBrace},
128	)
129}
130
131// statements is a list of assignment statements.
132// E.g statement: json.foo = "bar";
133type statements []statement
134
135// addWithValue takes a statement representing a path, copies it,
136// adds a value token to the end of the statement and appends
137// the new statement to the list of statements
138func (ss *statements) addWithValue(path statement, value token) {
139	s := make(statement, len(path), len(path)+3)
140	copy(s, path)
141	s = append(s, token{"=", typEquals}, value, token{";", typSemi})
142	*ss = append(*ss, s)
143}
144
145// add appends a new complete statement to list of statements
146func (ss *statements) add(s statement) {
147	*ss = append(*ss, s)
148}
149
150// Len returns the number of statements for sort.Sort
151func (ss statements) Len() int {
152	return len(ss)
153}
154
155// Swap swaps two statements for sort.Sort
156func (ss statements) Swap(i, j int) {
157	ss[i], ss[j] = ss[j], ss[i]
158}
159
160// a statementmaker is a function that makes a statement
161// from string
162type statementmaker func(str string) (statement, error)
163
164// statementFromString takes statement string, lexes it and returns
165// the corresponding statement
166func statementFromString(str string) statement {
167	l := newLexer(str)
168	s := l.lex()
169	return s
170}
171
172// statementmaker variant of statementFromString
173func statementFromStringMaker(str string) (statement, error) {
174	return statementFromString(str), nil
175}
176
177// statementFromJson returns statement encoded by
178// JSON specification
179func statementFromJSONSpec(str string) (statement, error) {
180	var a []interface{}
181	var ok bool
182	var v interface{}
183	var s statement
184	var t tokenTyp
185	var nstr string
186	var nbuf []byte
187
188	err := json.Unmarshal([]byte(str), &a)
189	if err != nil {
190		return nil, err
191	}
192	if len(a) != 2 {
193		goto out
194	}
195
196	v = a[1]
197	a, ok = a[0].([]interface{})
198	if !ok {
199		goto out
200	}
201
202	// We'll append one initial token, then 3 tokens for each element of a,
203	// then 3 closing tokens, that's alltogether 3*len(a)+4.
204	s = make(statement, 0, 3*len(a)+4)
205	s = append(s, token{"json", typBare})
206	for _, e := range a {
207		s = append(s, token{"[", typLBrace})
208		switch e := e.(type) {
209		case string:
210			s = append(s, token{quoteString(e), typQuotedKey})
211		case float64:
212			nbuf, err = json.Marshal(e)
213			if err != nil {
214				return nil, errors.Wrap(err, "JSON internal error")
215			}
216			nstr = fmt.Sprintf("%s", nbuf)
217			s = append(s, token{nstr, typNumericKey})
218		default:
219			ok = false
220			goto out
221		}
222		s = append(s, token{"]", typRBrace})
223	}
224
225	s = append(s, token{"=", typEquals})
226
227	switch v := v.(type) {
228	case bool:
229		if v {
230			t = typTrue
231		} else {
232			t = typFalse
233		}
234	case float64:
235		t = typNumber
236	case string:
237		t = typString
238	case []interface{}:
239		ok = (len(v) == 0)
240		if !ok {
241			goto out
242		}
243		t = typEmptyArray
244	case map[string]interface{}:
245		ok = (len(v) == 0)
246		if !ok {
247			goto out
248		}
249		t = typEmptyObject
250	default:
251		ok = (v == nil)
252		if !ok {
253			goto out
254		}
255		t = typNull
256	}
257
258	nbuf, err = json.Marshal(v)
259	if err != nil {
260		return nil, errors.Wrap(err, "JSON internal error")
261	}
262	nstr = fmt.Sprintf("%s", nbuf)
263	s = append(s, token{nstr, t})
264
265	s = append(s, token{";", typSemi})
266
267out:
268	if !ok {
269		return nil, errors.New("invalid JSON layout")
270	}
271	return s, nil
272}
273
274// ungron turns statements into a proper datastructure
275func (ss statements) toInterface() (interface{}, error) {
276
277	// Get all the individually parsed statements
278	var parsed []interface{}
279	for _, s := range ss {
280		u, err := ungronTokens(s)
281
282		switch err.(type) {
283		case nil:
284			// no problem :)
285		case errRecoverable:
286			continue
287		default:
288			return nil, errors.Wrapf(err, "ungron failed for `%s`", s)
289		}
290
291		parsed = append(parsed, u)
292	}
293
294	if len(parsed) == 0 {
295		return nil, fmt.Errorf("no statements were parsed")
296	}
297
298	merged := parsed[0]
299	for _, p := range parsed[1:] {
300		m, err := recursiveMerge(merged, p)
301		if err != nil {
302			return nil, errors.Wrap(err, "failed to merge statements")
303		}
304		merged = m
305	}
306	return merged, nil
307
308}
309
310// Less compares two statements for sort.Sort
311// Implements a natural sort to keep array indexes in order
312func (ss statements) Less(a, b int) bool {
313
314	// ss[a] and ss[b] are both slices of tokens. The first
315	// thing we need to do is find the first token (if any)
316	// that differs, then we can use that token to decide
317	// if ss[a] or ss[b] should come first in the sort.
318	diffIndex := -1
319	for i := range ss[a] {
320
321		if len(ss[b]) < i+1 {
322			// b must be shorter than a, so it
323			// should come first
324			return false
325		}
326
327		// The tokens match, so just carry on
328		if ss[a][i] == ss[b][i] {
329			continue
330		}
331
332		// We've found a difference
333		diffIndex = i
334		break
335	}
336
337	// If diffIndex is still -1 then the only difference must be
338	// that ss[b] is longer than ss[a], so ss[a] should come first
339	if diffIndex == -1 {
340		return true
341	}
342
343	// Get the tokens that differ
344	ta := ss[a][diffIndex]
345	tb := ss[b][diffIndex]
346
347	// An equals always comes first
348	if ta.typ == typEquals {
349		return true
350	}
351	if tb.typ == typEquals {
352		return false
353	}
354
355	// If both tokens are numeric keys do an integer comparison
356	if ta.typ == typNumericKey && tb.typ == typNumericKey {
357		ia, _ := strconv.Atoi(ta.text)
358		ib, _ := strconv.Atoi(tb.text)
359		return ia < ib
360	}
361
362	// If neither token is a number, just do a string comparison
363	if ta.typ != typNumber || tb.typ != typNumber {
364		return ta.text < tb.text
365	}
366
367	// We have two numbers to compare so turn them into json.Number
368	// for comparison
369	na, _ := json.Number(ta.text).Float64()
370	nb, _ := json.Number(tb.text).Float64()
371	return na < nb
372
373}
374
375// Contains searches the statements for a given statement
376// Mostly to make testing things easier
377func (ss statements) Contains(search statement) bool {
378	for _, i := range ss {
379		if reflect.DeepEqual(i, search) {
380			return true
381		}
382	}
383	return false
384}
385
386// statementsFromJSON takes an io.Reader containing JSON
387// and returns statements or an error on failure
388func statementsFromJSON(r io.Reader, prefix statement) (statements, error) {
389	var top interface{}
390	d := json.NewDecoder(r)
391	d.UseNumber()
392	err := d.Decode(&top)
393	if err != nil {
394		return nil, err
395	}
396	ss := make(statements, 0, 32)
397	ss.fill(prefix, top)
398	return ss, nil
399}
400
401// fill takes a prefix statement and some value and recursively fills
402// the statement list using that value
403func (ss *statements) fill(prefix statement, v interface{}) {
404
405	// Add a statement for the current prefix and value
406	ss.addWithValue(prefix, valueTokenFromInterface(v))
407
408	// Recurse into objects and arrays
409	switch vv := v.(type) {
410
411	case map[string]interface{}:
412		// It's an object
413		for k, sub := range vv {
414			if validIdentifier(k) {
415				ss.fill(prefix.withBare(k), sub)
416			} else {
417				ss.fill(prefix.withQuotedKey(k), sub)
418			}
419		}
420
421	case []interface{}:
422		// It's an array
423		for k, sub := range vv {
424			ss.fill(prefix.withNumericKey(k), sub)
425		}
426	}
427
428}
429