bombadillo/cmdparse/lexer.go

177 lines
3.4 KiB
Go

/*
* Copyright (C) 2022 Brian Evans
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package cmdparse
import (
"bufio"
"bytes"
"io"
"strings"
)
//------------------------------------------------\\
// + + + T Y P E S + + + \\
//--------------------------------------------------\\
type Token struct {
kind tok
val string
}
type scanner struct {
r *bufio.Reader
}
type tok int
//------------------------------------------------\\
// + + + V A R I A B L E S + + + \\
//--------------------------------------------------\\
var eof rune = rune(0)
const (
Word tok = iota
Action
Value
End
Whitespace
illegal
)
//------------------------------------------------\\
// + + + R E C E I V E R S + + + \\
//--------------------------------------------------\\
func (s *scanner) read() rune {
ch, _, err := s.r.ReadRune()
if err != nil {
return eof
}
return ch
}
func (s *scanner) scanText() Token {
var buf bytes.Buffer
buf.WriteRune(s.read())
for {
if ch := s.read(); ch == eof {
s.unread()
break
} else if !isLetter(ch) && !isDigit(ch) {
s.unread()
break
} else {
_, _ = buf.WriteRune(ch)
}
}
capInput := strings.ToUpper(buf.String())
switch capInput {
case "D", "DELETE", "A", "ADD", "W", "WRITE",
"S", "SET", "R", "RELOAD", "SEARCH",
"Q", "QUIT", "B", "BOOKMARKS", "H",
"HOME", "?", "HELP", "C", "CHECK",
"P", "PURGE", "JUMP", "J", "VERSION":
return Token{Action, capInput}
}
return Token{Word, buf.String()}
}
func (s *scanner) scanWhitespace() Token {
var buf bytes.Buffer
buf.WriteRune(s.read())
for {
if ch := s.read(); ch == eof {
s.unread()
break
} else if !isWhitespace(ch) {
s.unread()
break
} else {
_, _ = buf.WriteRune(ch)
}
}
return Token{Whitespace, buf.String()}
}
func (s *scanner) scanNumber() Token {
var buf bytes.Buffer
buf.WriteRune(s.read())
for {
if ch := s.read(); ch == eof {
break
} else if !isDigit(ch) {
s.unread()
break
} else {
_, _ = buf.WriteRune(ch)
}
}
return Token{Value, buf.String()}
}
func (s *scanner) unread() { _ = s.r.UnreadRune() }
func (s *scanner) scan() Token {
char := s.read()
if isWhitespace(char) {
s.unread()
return s.scanWhitespace()
} else if isDigit(char) {
s.unread()
return s.scanNumber()
} else if isLetter(char) {
s.unread()
return s.scanText()
}
if char == eof {
return Token{End, ""}
}
return Token{illegal, string(char)}
}
//------------------------------------------------\\
// + + + F U N C T I O N S + + + \\
//--------------------------------------------------\\
func NewScanner(r io.Reader) *scanner {
return &scanner{r: bufio.NewReader(r)}
}
func isWhitespace(ch rune) bool {
return ch == ' ' || ch == '\t' || ch == '\n'
}
func isLetter(ch rune) bool {
return ch >= '!' && ch <= '~'
}
func isDigit(ch rune) bool {
return ch >= '0' && ch <= '9'
}