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  }