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  }