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 }