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