github.com/1aal/kubeblocks@v0.0.0-20231107070852-e1c03e598921/pkg/unstructured/parser_fsm.go (about) 1 /* 2 Copyright (C) 2022-2023 ApeCloud Co., Ltd 3 4 This file is part of KubeBlocks project 5 6 This program is free software: you can redistribute it and/or modify 7 it under the terms of the GNU Affero General Public License as published by 8 the Free Software Foundation, either version 3 of the License, or 9 (at your option) any later version. 10 11 This program is distributed in the hope that it will be useful 12 but WITHOUT ANY WARRANTY; without even the implied warranty of 13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 GNU Affero General Public License for more details. 15 16 You should have received a copy of the GNU Affero General Public License 17 along with this program. If not, see <http://www.gnu.org/licenses/>. 18 */ 19 20 package unstructured 21 22 import ( 23 "fmt" 24 "unicode/utf8" 25 ) 26 27 // stateHandleFn represents the state of the scanner as a function that returns the next state. 28 type stateHandleFn func(*fsm) stateHandleFn 29 30 // fsm is finite state machine model of the redis configuration parser. 31 // The output of fsm is a parameter name and list of parameter Values. 32 // reference c++ code implementation: https://github.com/redis/redis/blob/unstable/src/sds.c#L1082 33 type fsm struct { 34 param *Item 35 36 // split chars 37 splitCharacters string 38 39 // the string being scanned 40 input string 41 currentPos int // current position in the input 42 lastScanWith int // width of last rune read from input 43 44 err error 45 // scanned token 46 token []rune 47 } 48 49 func (f *fsm) parse(line string) error { 50 f.input = line 51 52 for handle := prepareScan(f); handle != nil; { 53 handle = handle(f) 54 } 55 return f.err 56 } 57 58 func (f *fsm) empty() bool { 59 return f.currentPos >= len(f.input) 60 } 61 62 func (f *fsm) next() rune { 63 if f.empty() { 64 f.lastScanWith = 0 65 return eof 66 } 67 r, w := utf8.DecodeRuneInString(f.input[f.currentPos:]) 68 f.lastScanWith = w 69 f.currentPos += f.lastScanWith 70 return r 71 } 72 73 func (f *fsm) appendRune(r rune) { 74 f.token = append(f.token, r) 75 } 76 77 func (f *fsm) peek() rune { 78 r := f.next() 79 f.currentPos -= f.lastScanWith 80 return r 81 } 82 83 func prepareScan(f *fsm) stateHandleFn { 84 switch r := f.next(); { 85 case isEOF(r): 86 return stateEOF 87 case isSplitCharacter(r): 88 return stateTokenEnd 89 case isQuotes(r): 90 return stateQuotesString 91 case isSingleQuotes(r): 92 return stateSQuotesString 93 default: 94 f.appendRune(r) 95 return prepareScan 96 } 97 } 98 99 func stateFailed(f *fsm, format string, args ...interface{}) stateHandleFn { 100 f.err = fmt.Errorf(format, args...) 101 return nil 102 } 103 104 func stateEOF(f *fsm) stateHandleFn { 105 if len(f.token) > 0 { 106 stateTokenEnd(f) 107 } 108 return nil 109 } 110 111 func stateTokenEnd(f *fsm) stateHandleFn { 112 f.param.addToken(string(f.token)) 113 f.token = f.token[:0] 114 return stateTrimSplitCharacters 115 } 116 117 func stateTrimSplitCharacters(f *fsm) stateHandleFn { 118 for r := f.peek(); !isEOF(r) && isSplitCharacter(r); { 119 f.next() 120 r = f.peek() 121 } 122 return prepareScan 123 } 124 125 func stateQuotesString(f *fsm) stateHandleFn { 126 switch r := f.next(); { 127 default: 128 f.appendRune(r) 129 case isEOF(r): 130 return stateFailed(f, "unterminated quotes.") 131 case isQuotes(r): 132 n := f.peek() 133 if !isEOF(n) && !isSplitCharacter(n) { 134 return stateFailed(f, "closing quote must be followed by a space or end.") 135 } 136 return stateTokenEnd 137 case isEscape(r): 138 if isEOF(f.peek()) { 139 return stateFailed(f, "unterminated quotes.") 140 } 141 stateEscape(f) 142 } 143 return stateQuotesString 144 } 145 146 func stateEscape(f *fsm) { 147 r := f.next() 148 switch { 149 default: 150 f.appendRune(r) 151 case isEOF(r): 152 // do nothing 153 case r == 'n': 154 f.appendRune('\n') 155 case r == 'r': 156 f.appendRune('\r') 157 case r == 't': 158 f.appendRune('\t') 159 case r == 'b': 160 f.appendRune('\b') 161 case r == 'a': 162 f.appendRune('\a') 163 } 164 } 165 166 func stateSQuotesString(f *fsm) stateHandleFn { 167 switch r := f.next(); { 168 default: 169 f.appendRune(r) 170 case isEOF(r): 171 return stateFailed(f, "unterminated single quotes.") 172 case isSingleQuotes(r): 173 n := f.peek() 174 if !isEOF(n) && !isSplitCharacter(n) { 175 return stateFailed(f, "closing quote must be followed by a space or end.") 176 } 177 return stateTokenEnd 178 case isEscape(r): 179 if isSingleQuotes(f.peek()) { 180 f.appendRune(singleQuotes) 181 f.next() 182 } 183 } 184 return stateSQuotesString 185 }