github.com/lmorg/murex@v0.0.0-20240217211045-e081c89cd4ef/utils/mxjson/parser.go (about) 1 package mxjson 2 3 import ( 4 "fmt" 5 "strconv" 6 ) 7 8 // Parse converts mxjson file into a Go struct 9 func Parse(json []byte) (interface{}, error) { 10 if len(json) == 0 { 11 return nil, nil 12 } 13 var ( 14 state parserState // a lazy way of bypassing the need to build ASTs 15 i, y, x = 0, 1, 0 // cursor position 16 b byte // current character 17 err error // any errors 18 current *str // pointer for strings 19 value = newStr() // current value stored as a string 20 valType objectType // data type for value 21 objects = newObjs() // cursor inside nested objects 22 comment bool // cursor inside a comment? 23 escape bool // next character escaped? 24 unquote quote // cursor inside an unquoted block? 25 qSingle quote // cursor inside a ' quote? 26 qDouble quote // cursor inside a " quote? 27 qBrace = newPair() // cursor inside a ( quote? 28 square = newPair() // cursor inside a [ block? 29 curly = newPair() // cursor inside a { block? 30 ) 31 32 cannotClose := func() (interface{}, error) { 33 return nil, fmt.Errorf("cannot close `%s` at %d(%d,%d): %s", string([]byte{b}), i+1, y, x, err.Error()) 34 } 35 36 unexpectedCharacter := func() (interface{}, error) { 37 return nil, fmt.Errorf("unexpected character `%s` at %d(%d,%d)", string([]byte{b}), i+1, y, x) 38 } 39 40 unexpectedColon := func() (interface{}, error) { 41 return nil, fmt.Errorf("unexpected `%s` at %d(%d,%d). Colons should just be used to separate keys and values", string([]byte{b}), i+1, y, x) 42 } 43 44 unexpectedComma := func() (interface{}, error) { 45 return nil, fmt.Errorf("unexpected `%s` at %d(%d,%d). Commas should just be used to separate items mid arrays and maps and not for the end value nor to separate keys and values in a map", string([]byte{b}), i+1, y, x) 46 } 47 48 invalidNewLine := func() (interface{}, error) { 49 return nil, fmt.Errorf("cannot have a new line (eg \\n) within single nor double quotes at %d(%d,%d)", i+1, y, x) 50 } 51 52 cannotOpen := func() (interface{}, error) { 53 return nil, fmt.Errorf("cannot use the brace quotes on key names at %d(%d,%d)", i+1, y, x) 54 } 55 56 cannotReOpen := func() (interface{}, error) { 57 return nil, fmt.Errorf("quote multiple strings in a key or value block at %d(%d,%d). Strings should be comma separated and inside arrays block (`[` and `]`) where multiple values are expected", i+1, y, x) 58 } 59 60 keysOutsideMap := func() (interface{}, error) { 61 return nil, fmt.Errorf("keys outside of map blocks, `{...}`, at %d(%d,%d)", i+1, y, x) 62 } 63 64 storeErr := func(err error, pos, y, x int) error { 65 if err != nil { 66 return fmt.Errorf("error at %d(%d,%d):\n%s", pos, y, x, err.Error()) 67 } 68 69 return nil 70 } 71 72 /*cannotMixArrayTypes := func() (interface{}, error) { 73 return nil, fmt.Errorf("Cannot mix array types at %d(%d,%d)", i+1,x,y) 74 }*/ 75 76 store := func() error { 77 state++ 78 79 if state != stateEndVal { 80 return nil 81 } 82 83 pos := i - current.len + 1 84 85 switch valType { 86 case objBoolean: 87 s := current.String() 88 switch s { 89 case "true": 90 return storeErr(objects.SetValue(true), pos, y, x) 91 case "false": 92 return storeErr(objects.SetValue(false), pos, y, x) 93 default: 94 return fmt.Errorf("boolean values should be either 'true' or 'false', instead received '%s' at %d(%d,%d)", s, pos, y, x) 95 } 96 97 case objNumber: 98 i, err := strconv.ParseFloat(current.String(), 64) 99 if err != nil { 100 return fmt.Errorf("%s at %d(%d,%d)", err.Error(), pos, y, x) 101 } 102 return storeErr(objects.SetValue(i), pos, y, x) 103 104 case objString: 105 return storeErr(objects.SetValue(current.String()), pos, y, x) 106 107 default: 108 return fmt.Errorf("unexpected condition in `Parse(json []byte) (interface{}, error).\nThis is likely a murex bug, please log an issue at https://github.com/lmorg/murex/issues`") 109 } 110 } 111 112 for ; i < len(json); i++ { 113 b = json[i] 114 x++ 115 116 if comment { 117 if b == '\n' { 118 comment = false 119 } 120 continue 121 } 122 123 switch b { 124 case '#': 125 comment = true 126 127 case '\r': 128 // do nothing 129 130 case '\n': 131 y++ 132 x = 0 133 switch { 134 case qSingle.IsOpen(), qDouble.IsOpen(): 135 return invalidNewLine() 136 case qBrace.IsOpen(): 137 current.Append(b) 138 case unquote.IsOpen(): 139 unquote.Close() 140 err = store() 141 if err != nil { 142 return nil, err 143 } 144 default: 145 // do nothing 146 } 147 148 case ' ', '\t': 149 switch { 150 case qSingle.IsOpen(), qDouble.IsOpen(): 151 current.Append(b) 152 case qBrace.IsOpen(): 153 current.Append(b) 154 case unquote.IsOpen(): 155 unquote.Close() 156 err = store() 157 if err != nil { 158 return nil, err 159 } 160 default: 161 // do nothing 162 } 163 164 case '\\': 165 switch { 166 case qSingle.IsOpen(), qDouble.IsOpen(), qBrace.IsOpen(): 167 escape = !escape 168 if !escape { 169 current.Append(b) 170 } 171 default: 172 return unexpectedCharacter() 173 } 174 175 case '\'': 176 switch { 177 case escape: 178 escape = false 179 current.Append(b) 180 case unquote.IsOpen(): 181 return unexpectedCharacter() 182 case qDouble.IsOpen(), qBrace.IsOpen(): 183 current.Append(b) 184 case qSingle.IsOpen(): 185 qSingle.Close() 186 state++ 187 if state == stateEndVal { 188 objects.SetValue(current.String()) 189 } 190 case state == stateBeginKey: 191 if objects.len < 0 { 192 return keysOutsideMap() 193 } 194 qSingle.Open(i) 195 current = objects.GetKeyPtr() 196 case state == stateBeginVal: 197 qSingle.Open(i) 198 current = value 199 valType = objString 200 default: 201 return cannotReOpen() 202 } 203 204 case '"': 205 switch { 206 case escape: 207 escape = false 208 current.Append(b) 209 case unquote.IsOpen(): 210 return unexpectedCharacter() 211 case qSingle.IsOpen(), qBrace.IsOpen(): 212 current.Append(b) 213 case qDouble.IsOpen(): 214 qDouble.Close() 215 err = store() 216 if err != nil { 217 return nil, err 218 } 219 case state == stateBeginKey: 220 if objects.len < 0 { 221 return keysOutsideMap() 222 } 223 qDouble.Open(i) 224 current = objects.GetKeyPtr() 225 case state == stateBeginVal: 226 qDouble.Open(i) 227 current = value 228 valType = objString 229 default: 230 return cannotReOpen() 231 } 232 233 case '(': 234 switch { 235 case escape: 236 escape = false 237 current.Append(b) 238 case unquote.IsOpen(): 239 return unexpectedCharacter() 240 case qSingle.IsOpen(), qDouble.IsOpen(): 241 current.Append(b) 242 case qBrace.IsOpen(): 243 current.Append(b) 244 qBrace.Open(i) 245 default: 246 if state != stateBeginKey && state != stateBeginVal { 247 return cannotOpen() 248 } 249 qBrace.Open(i) 250 current = value 251 valType = objString 252 } 253 254 case ')': 255 switch { 256 case escape: 257 escape = false 258 current.Append(b) 259 case unquote.IsOpen(): 260 return unexpectedCharacter() 261 case qSingle.IsOpen(), qDouble.IsOpen(): 262 current.Append(b) 263 case qBrace.len > 1: 264 current.Append(b) 265 qBrace.Close() 266 default: 267 err = qBrace.Close() 268 if err != nil { 269 return cannotClose() 270 } 271 //state++ 272 //objects.SetValue(current.String()) 273 err = store() 274 if err != nil { 275 return nil, err 276 } 277 } 278 279 case '{': 280 switch { 281 case escape: 282 escape = false 283 current.Append(b) 284 case unquote.IsOpen(): 285 return unexpectedCharacter() 286 case qSingle.IsOpen(), qDouble.IsOpen(), qBrace.IsOpen(): 287 current.Append(b) 288 default: 289 state = stateBeginKey 290 curly.Open(i) 291 objects.New(objMap) 292 } 293 294 case '}': 295 switch { 296 case escape: 297 escape = false 298 current.Append(b) 299 case qSingle.IsOpen(), qDouble.IsOpen(), qBrace.IsOpen(): 300 current.Append(b) 301 case unquote.IsOpen(): 302 unquote.Close() 303 err = store() 304 if err != nil { 305 return nil, err 306 } 307 fallthrough 308 default: 309 err = curly.Close() 310 if err != nil { 311 return cannotClose() 312 } 313 state++ 314 objects.MergeDown() 315 } 316 317 case '[': 318 switch { 319 case escape: 320 escape = false 321 current.Append(b) 322 case unquote.IsOpen(): 323 return unexpectedCharacter() 324 case qSingle.IsOpen(), qDouble.IsOpen(), qBrace.IsOpen(): 325 current.Append(b) 326 default: 327 state = stateBeginVal 328 square.Open(i) 329 objects.New(objArrayUndefined) 330 } 331 332 case ']': 333 switch { 334 case escape: 335 escape = false 336 current.Append(b) 337 case qSingle.IsOpen(), qDouble.IsOpen(), qBrace.IsOpen(): 338 current.Append(b) 339 case unquote.IsOpen(): 340 unquote.Close() 341 err = store() 342 if err != nil { 343 return nil, err 344 } 345 fallthrough 346 default: 347 err = square.Close() 348 if err != nil { 349 return cannotClose() 350 } 351 state++ 352 objects.MergeDown() 353 } 354 355 case ':': 356 switch { 357 case escape: 358 escape = false 359 current.Append(b) 360 case qSingle.IsOpen(), qDouble.IsOpen(), qBrace.IsOpen(): 361 current.Append(b) 362 case unquote.IsOpen(): 363 return unexpectedCharacter() 364 case state != stateEndKey: 365 return unexpectedColon() 366 default: 367 state++ 368 } 369 370 case ',': 371 switch { 372 case escape: 373 escape = false 374 current.Append(b) 375 case qSingle.IsOpen(), qDouble.IsOpen(), qBrace.IsOpen(): 376 current.Append(b) 377 case unquote.IsOpen(): 378 unquote.Close() 379 err = store() 380 if err != nil { 381 return nil, err 382 } 383 fallthrough 384 case state > stateBeginVal: 385 switch objects.GetObjType() { 386 case objMap: 387 state = stateBeginKey 388 case objUndefined: 389 return unexpectedComma() 390 default: 391 state = stateBeginVal 392 } 393 default: 394 return unexpectedComma() 395 } 396 397 case 't', 'r', 'u', 'e', 398 'f', 'a', 'l', 's': 399 switch { 400 case escape: 401 escape = false 402 current.Append(b) 403 case qSingle.IsOpen(), qDouble.IsOpen(), qBrace.IsOpen(): 404 current.Append(b) 405 case unquote.IsOpen(): 406 current.Append(b) 407 case state == stateBeginVal: 408 unquote.Open(i) 409 current = value 410 current.Append(b) 411 valType = objBoolean 412 default: 413 return unexpectedCharacter() 414 } 415 416 case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '.', '-': 417 switch { 418 case escape: 419 escape = false 420 current.Append(b) 421 case qSingle.IsOpen(), qDouble.IsOpen(), qBrace.IsOpen(): 422 current.Append(b) 423 case unquote.IsOpen(): 424 current.Append(b) 425 case state == stateBeginVal: 426 unquote.Open(i) 427 current = value 428 current.Append(b) 429 valType = objNumber 430 default: 431 return unexpectedCharacter() 432 } 433 434 default: 435 switch { 436 case escape: 437 escape = false 438 current.Append(b) 439 case unquote.IsOpen(): 440 return unexpectedCharacter() 441 case qSingle.IsOpen(), qDouble.IsOpen(), qBrace.IsOpen(): 442 current.Append(b) 443 default: 444 return unexpectedCharacter() 445 } 446 } 447 448 } 449 450 switch { 451 case qSingle.IsOpen(): 452 return nil, fmt.Errorf("single quote, `'`, opened at %d but not closed", qSingle.pos+1) 453 454 case qDouble.IsOpen(): 455 return nil, fmt.Errorf("double quote, `\"`, opened at %d but not closed", qDouble.pos+1) 456 457 case qBrace.IsOpen(): 458 return nil, fmt.Errorf("quote brace, `(`, opened at %d but not closed", qBrace.pos[qBrace.len]+1) 459 460 case square.IsOpen(): 461 return nil, fmt.Errorf("square brace, `(`, opened at %d but not closed", square.pos[square.len]+1) 462 463 case curly.IsOpen(): 464 return nil, fmt.Errorf("curly brace, `(`, opened at %d but not closed", curly.pos[curly.len]+1) 465 466 default: 467 return objects.nest[0].value, nil 468 } 469 }