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  }