github.com/mithrandie/csvq@v1.18.1/lib/json/query_scanner.go (about)

     1  package json
     2  
     3  import (
     4  	"errors"
     5  	"strings"
     6  	"unicode"
     7  
     8  	"github.com/mithrandie/go-text/json"
     9  )
    10  
    11  const AliasSpecifier = "AS"
    12  
    13  type QueryScanner struct {
    14  	src    []rune
    15  	srcPos int
    16  	offset int
    17  
    18  	column int
    19  
    20  	err error
    21  }
    22  
    23  func (s *QueryScanner) Init(src string) *QueryScanner {
    24  	s.src = []rune(src)
    25  	s.srcPos = 0
    26  	s.offset = 0
    27  	s.column = 0
    28  
    29  	return s
    30  }
    31  
    32  func (s *QueryScanner) peek() rune {
    33  	if len(s.src) <= s.srcPos {
    34  		return EOF
    35  	}
    36  	return s.src[s.srcPos]
    37  }
    38  
    39  func (s *QueryScanner) next() rune {
    40  	ch := s.peek()
    41  	if ch == EOF {
    42  		return ch
    43  	}
    44  
    45  	s.srcPos++
    46  	s.offset++
    47  	s.column++
    48  	return ch
    49  }
    50  
    51  func (s *QueryScanner) runes() []rune {
    52  	return s.src[(s.srcPos - s.offset):s.srcPos]
    53  }
    54  
    55  func (s *QueryScanner) literal() string {
    56  	return string(s.runes())
    57  }
    58  
    59  func (s *QueryScanner) trimQuotes() string {
    60  	runes := s.runes()
    61  	quote := runes[0]
    62  	switch quote {
    63  	case '"', '\'', '`':
    64  		if 1 < len(runes) && runes[0] == quote && runes[len(runes)-1] == quote {
    65  			runes = runes[1:(len(runes) - 1)]
    66  		}
    67  	}
    68  	return string(runes)
    69  }
    70  
    71  func (s *QueryScanner) Scan() (QueryToken, error) {
    72  	ch := s.skipSpaces()
    73  
    74  	s.offset = 0
    75  	s.next()
    76  
    77  	token := ch
    78  	literal := string(ch)
    79  	column := s.column
    80  
    81  	switch {
    82  	case s.isDecimal(ch):
    83  		s.scanDecimal()
    84  		literal = s.literal()
    85  		token = PATH_INDEX
    86  	case s.isIdentRune(ch):
    87  		s.scanIdentifier()
    88  		literal = s.literal()
    89  
    90  		if strings.EqualFold(AliasSpecifier, literal) {
    91  			token = AS
    92  		} else {
    93  			token = PATH_IDENTIFIER
    94  		}
    95  	default:
    96  		switch ch {
    97  		case EOF:
    98  			break
    99  		case '"', '\'', '`':
   100  			s.scanString(ch)
   101  			literal, _ = json.Unescape(s.trimQuotes())
   102  			token = PATH_IDENTIFIER
   103  		}
   104  	}
   105  
   106  	return QueryToken{Token: int(token), Literal: literal, Column: column}, s.err
   107  }
   108  
   109  func (s *QueryScanner) skipSpaces() rune {
   110  	for unicode.IsSpace(s.peek()) {
   111  		s.next()
   112  	}
   113  	return s.peek()
   114  }
   115  
   116  func (s *QueryScanner) isDecimal(ch rune) bool {
   117  	return '0' <= ch && ch <= '9'
   118  }
   119  
   120  func (s *QueryScanner) isIdentRune(ch rune) bool {
   121  	return ch == '_' || ch == '$' || unicode.IsLetter(ch) || unicode.IsDigit(ch)
   122  }
   123  
   124  func (s *QueryScanner) scanIdentifier() {
   125  	for s.isIdentRune(s.peek()) {
   126  		s.next()
   127  	}
   128  }
   129  
   130  func (s *QueryScanner) scanString(quote rune) {
   131  	for {
   132  		ch := s.next()
   133  		if ch == EOF {
   134  			s.err = errors.New("string not terminated")
   135  			break
   136  		}
   137  
   138  		if ch == quote {
   139  			break
   140  		}
   141  
   142  		if ch == '\\' {
   143  			switch s.peek() {
   144  			case quote:
   145  				s.next()
   146  			}
   147  		}
   148  	}
   149  }
   150  
   151  func (s *QueryScanner) scanDecimal() {
   152  	for s.isDecimal(s.peek()) {
   153  		s.next()
   154  	}
   155  }