github.com/mithrandie/csvq@v1.18.1/lib/excmd/argument_scanner.go (about) 1 package excmd 2 3 import ( 4 "bytes" 5 "errors" 6 "unicode" 7 8 "github.com/mithrandie/csvq/lib/parser" 9 ) 10 11 type ArgumentScanner struct { 12 src []rune 13 srcPos int 14 text bytes.Buffer 15 elementType ElementType 16 17 err error 18 } 19 20 func (s *ArgumentScanner) Init(src string) *ArgumentScanner { 21 s.src = []rune(src) 22 s.srcPos = 0 23 s.text.Reset() 24 s.elementType = 0 25 s.err = nil 26 return s 27 } 28 29 func (s *ArgumentScanner) Err() error { 30 return s.err 31 } 32 33 func (s *ArgumentScanner) Text() string { 34 return s.text.String() 35 } 36 37 func (s *ArgumentScanner) ElementType() ElementType { 38 return s.elementType 39 } 40 41 func (s *ArgumentScanner) peek() rune { 42 if len(s.src) <= s.srcPos { 43 return EOF 44 } 45 46 return s.src[s.srcPos] 47 } 48 49 func (s *ArgumentScanner) next() rune { 50 ch := s.peek() 51 if ch == EOF { 52 return ch 53 } 54 55 s.srcPos++ 56 return ch 57 } 58 59 func (s *ArgumentScanner) Scan() bool { 60 s.text.Reset() 61 62 switch s.peek() { 63 case EOF: 64 return false 65 case parser.VariableSign: 66 s.next() 67 68 switch s.peek() { 69 case parser.EnvironmentVariableSign: 70 s.next() 71 s.elementType = EnvironmentVariable 72 case parser.RuntimeInformationSign: 73 s.next() 74 s.elementType = RuntimeInformation 75 default: 76 s.elementType = Variable 77 } 78 79 if s.elementType == EnvironmentVariable && s.peek() == '`' { 80 s.scanQuotedEnvironmentVariable(s.next()) 81 } else { 82 s.scanVariable() 83 } 84 85 if s.text.Len() < 1 { 86 s.err = errors.New("invalid variable symbol") 87 } 88 case parser.ExternalCommandSign: 89 s.next() 90 91 s.elementType = CsvqExpression 92 if s.peek() != parser.BeginExpression { 93 s.err = errors.New("invalid command symbol") 94 } else { 95 s.next() 96 s.scanCsvqExpression() 97 } 98 default: 99 s.elementType = FixedString 100 s.scanString() 101 } 102 103 return s.err == nil 104 } 105 106 func (s *ArgumentScanner) scanQuotedEnvironmentVariable(quote rune) { 107 for { 108 ch := s.next() 109 if ch == EOF { 110 s.err = errors.New("environment variable not terminated") 111 break 112 } 113 114 if ch == quote { 115 break 116 } 117 118 if ch == '\\' { 119 switch s.peek() { 120 case '\\', quote: 121 ch = s.next() 122 } 123 } 124 s.text.WriteRune(ch) 125 } 126 } 127 128 func (s *ArgumentScanner) scanString() { 129 for { 130 ch := s.peek() 131 if ch == parser.VariableSign || ch == parser.ExternalCommandSign || ch == EOF { 132 break 133 } 134 135 if ch != '\\' { 136 s.text.WriteRune(s.next()) 137 continue 138 } 139 140 s.next() 141 switch s.peek() { 142 case parser.VariableSign, parser.ExternalCommandSign: 143 s.text.WriteRune(s.next()) 144 default: 145 s.text.WriteRune('\\') 146 } 147 } 148 } 149 150 func (s *ArgumentScanner) scanVariable() { 151 for s.isVariableRune(s.peek()) { 152 s.text.WriteRune(s.next()) 153 } 154 } 155 156 func (s *ArgumentScanner) isVariableRune(ch rune) bool { 157 return ch == '_' || unicode.IsLetter(ch) || unicode.IsDigit(ch) 158 } 159 160 func (s *ArgumentScanner) scanCsvqExpression() { 161 for { 162 ch := s.next() 163 if ch == EOF { 164 s.err = errors.New("command not terminated") 165 break 166 } 167 168 if ch == parser.EndExpression { 169 break 170 } 171 172 if ch == '\\' { 173 switch s.peek() { 174 case parser.BeginExpression, parser.EndExpression: 175 ch = s.next() 176 } 177 } 178 s.text.WriteRune(ch) 179 } 180 }