box-o-sand/argh/scanner.go

191 lines
2.9 KiB
Go
Raw Normal View History

package argh
// NOTE: much of this is lifted from
// https://blog.gopheracademy.com/advent-2014/parsers-lexers/
import (
"bufio"
"bytes"
"errors"
"io"
"log"
"unicode"
)
const (
nul = rune(0)
eol = rune(-1)
)
var (
DefaultScannerConfig = &ScannerConfig{
AssignmentOperator: '=',
FlagPrefix: '-',
MultiValueDelim: ',',
}
)
type Scanner struct {
r *bufio.Reader
cfg *ScannerConfig
}
type ScannerConfig struct {
AssignmentOperator rune
FlagPrefix rune
MultiValueDelim rune
Commands []string
}
func NewScanner(r io.Reader, cfg *ScannerConfig) *Scanner {
if cfg == nil {
cfg = DefaultScannerConfig
}
return &Scanner{
r: bufio.NewReader(r),
cfg: cfg,
}
}
func (s *Scanner) Scan() (Token, string) {
ch := s.read()
if s.isBlankspace(ch) {
s.unread()
return s.scanBlankspace()
}
if s.isAssignmentOperator(ch) {
return ASSIGN, string(ch)
}
if s.isMultiValueDelim(ch) {
return MULTI_VALUE_DELIMITER, string(ch)
}
if ch == eol {
return EOL, ""
}
if ch == nul {
return ARG_DELIMITER, string(ch)
}
if unicode.IsGraphic(ch) {
s.unread()
return s.scanArg()
}
return ILLEGAL, string(ch)
}
func (s *Scanner) read() rune {
ch, _, err := s.r.ReadRune()
if errors.Is(err, io.EOF) {
return eol
} else if err != nil {
log.Printf("unknown scanner error=%+v", err)
return eol
}
return ch
}
func (s *Scanner) unread() {
_ = s.r.UnreadRune()
}
func (s *Scanner) isBlankspace(ch rune) bool {
return ch == ' ' || ch == '\t' || ch == '\n'
}
func (s *Scanner) isUnderscore(ch rune) bool {
return ch == '_'
}
func (s *Scanner) isFlagPrefix(ch rune) bool {
return ch == s.cfg.FlagPrefix
}
func (s *Scanner) isMultiValueDelim(ch rune) bool {
return ch == s.cfg.MultiValueDelim
}
func (s *Scanner) isAssignmentOperator(ch rune) bool {
return ch == s.cfg.AssignmentOperator
}
func (s *Scanner) scanBlankspace() (Token, string) {
buf := &bytes.Buffer{}
buf.WriteRune(s.read())
for {
if ch := s.read(); ch == eol {
break
} else if !s.isBlankspace(ch) {
s.unread()
break
} else {
_, _ = buf.WriteRune(ch)
}
}
return BS, buf.String()
}
func (s *Scanner) scanArg() (Token, string) {
buf := &bytes.Buffer{}
buf.WriteRune(s.read())
for {
ch := s.read()
if ch == eol || ch == nul || s.isAssignmentOperator(ch) || s.isMultiValueDelim(ch) {
s.unread()
break
}
_, _ = buf.WriteRune(ch)
}
str := buf.String()
if len(str) == 0 {
return EMPTY, str
}
ch0 := rune(str[0])
if len(str) == 1 {
if s.isFlagPrefix(ch0) {
return STDIN_FLAG, str
}
return IDENT, str
}
ch1 := rune(str[1])
if len(str) == 2 {
if str == string(s.cfg.FlagPrefix)+string(s.cfg.FlagPrefix) {
return STOP_FLAG, str
}
if s.isFlagPrefix(ch0) {
return SHORT_FLAG, str
}
}
if s.isFlagPrefix(ch0) {
if s.isFlagPrefix(ch1) {
return LONG_FLAG, str
}
return COMPOUND_SHORT_FLAG, str
}
return IDENT, str
}