242 lines
4.1 KiB
Go
242 lines
4.1 KiB
Go
package ssh_config
|
|
|
|
import (
|
|
"io"
|
|
|
|
buffruneio "github.com/pelletier/go-buffruneio"
|
|
)
|
|
|
|
// Define state functions
|
|
type sshLexStateFn func() sshLexStateFn
|
|
|
|
type sshLexer struct {
|
|
input *buffruneio.Reader // Textual source
|
|
buffer []rune // Runes composing the current token
|
|
tokens chan token
|
|
line uint32
|
|
col uint16
|
|
endbufferLine uint32
|
|
endbufferCol uint16
|
|
}
|
|
|
|
func (s *sshLexer) lexComment(previousState sshLexStateFn) sshLexStateFn {
|
|
return func() sshLexStateFn {
|
|
growingString := ""
|
|
for next := s.peek(); next != '\n' && next != eof; next = s.peek() {
|
|
if next == '\r' && s.follow("\r\n") {
|
|
break
|
|
}
|
|
growingString += string(next)
|
|
s.next()
|
|
}
|
|
s.emitWithValue(tokenComment, growingString)
|
|
s.skip()
|
|
return previousState
|
|
}
|
|
}
|
|
|
|
// lex the space after an equals sign in a function
|
|
func (s *sshLexer) lexRspace() sshLexStateFn {
|
|
for {
|
|
next := s.peek()
|
|
if !isSpace(next) {
|
|
break
|
|
}
|
|
s.skip()
|
|
}
|
|
return s.lexRvalue
|
|
}
|
|
|
|
func (s *sshLexer) lexEquals() sshLexStateFn {
|
|
for {
|
|
next := s.peek()
|
|
if next == '=' {
|
|
s.emit(tokenEquals)
|
|
s.skip()
|
|
return s.lexRspace
|
|
}
|
|
// TODO error handling here; newline eof etc.
|
|
if !isSpace(next) {
|
|
break
|
|
}
|
|
s.skip()
|
|
}
|
|
return s.lexRvalue
|
|
}
|
|
|
|
func (s *sshLexer) lexKey() sshLexStateFn {
|
|
growingString := ""
|
|
|
|
for r := s.peek(); isKeyChar(r); r = s.peek() {
|
|
// simplified a lot here
|
|
if isSpace(r) || r == '=' {
|
|
s.emitWithValue(tokenKey, growingString)
|
|
s.skip()
|
|
return s.lexEquals
|
|
}
|
|
growingString += string(r)
|
|
s.next()
|
|
}
|
|
s.emitWithValue(tokenKey, growingString)
|
|
return s.lexEquals
|
|
}
|
|
|
|
func (s *sshLexer) lexRvalue() sshLexStateFn {
|
|
growingString := ""
|
|
for {
|
|
next := s.peek()
|
|
switch next {
|
|
case '\r':
|
|
if s.follow("\r\n") {
|
|
s.emitWithValue(tokenString, growingString)
|
|
s.skip()
|
|
return s.lexVoid
|
|
}
|
|
case '\n':
|
|
s.emitWithValue(tokenString, growingString)
|
|
s.skip()
|
|
return s.lexVoid
|
|
case '#':
|
|
s.emitWithValue(tokenString, growingString)
|
|
s.skip()
|
|
return s.lexComment(s.lexVoid)
|
|
case eof:
|
|
s.next()
|
|
}
|
|
if next == eof {
|
|
break
|
|
}
|
|
growingString += string(next)
|
|
s.next()
|
|
}
|
|
s.emit(tokenEOF)
|
|
return nil
|
|
}
|
|
|
|
func (s *sshLexer) read() rune {
|
|
r, _, err := s.input.ReadRune()
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
if r == '\n' {
|
|
s.endbufferLine++
|
|
s.endbufferCol = 1
|
|
} else {
|
|
s.endbufferCol++
|
|
}
|
|
return r
|
|
}
|
|
|
|
func (s *sshLexer) next() rune {
|
|
r := s.read()
|
|
|
|
if r != eof {
|
|
s.buffer = append(s.buffer, r)
|
|
}
|
|
return r
|
|
}
|
|
|
|
func (s *sshLexer) lexVoid() sshLexStateFn {
|
|
for {
|
|
next := s.peek()
|
|
switch next {
|
|
case '#':
|
|
s.skip()
|
|
return s.lexComment(s.lexVoid)
|
|
case '\r':
|
|
fallthrough
|
|
case '\n':
|
|
s.emit(tokenEmptyLine)
|
|
s.skip()
|
|
continue
|
|
}
|
|
|
|
if isSpace(next) {
|
|
s.skip()
|
|
}
|
|
|
|
if isKeyStartChar(next) {
|
|
return s.lexKey
|
|
}
|
|
|
|
// removed IsKeyStartChar and lexKey. probably will need to readd
|
|
|
|
if next == eof {
|
|
s.next()
|
|
break
|
|
}
|
|
}
|
|
|
|
s.emit(tokenEOF)
|
|
return nil
|
|
}
|
|
|
|
func (s *sshLexer) ignore() {
|
|
s.buffer = make([]rune, 0)
|
|
s.line = s.endbufferLine
|
|
s.col = s.endbufferCol
|
|
}
|
|
|
|
func (s *sshLexer) skip() {
|
|
s.next()
|
|
s.ignore()
|
|
}
|
|
|
|
func (s *sshLexer) emit(t tokenType) {
|
|
s.emitWithValue(t, string(s.buffer))
|
|
}
|
|
|
|
func (s *sshLexer) emitWithValue(t tokenType, value string) {
|
|
tok := token{
|
|
Position: Position{s.line, s.col},
|
|
typ: t,
|
|
val: value,
|
|
}
|
|
s.tokens <- tok
|
|
s.ignore()
|
|
}
|
|
|
|
func (s *sshLexer) peek() rune {
|
|
r, _, err := s.input.ReadRune()
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
s.input.UnreadRune()
|
|
return r
|
|
}
|
|
|
|
func (s *sshLexer) follow(next string) bool {
|
|
for _, expectedRune := range next {
|
|
r, _, err := s.input.ReadRune()
|
|
defer s.input.UnreadRune()
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
if expectedRune != r {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
func (s *sshLexer) run() {
|
|
for state := s.lexVoid; state != nil; {
|
|
state = state()
|
|
}
|
|
close(s.tokens)
|
|
}
|
|
|
|
func lexSSH(input io.Reader) chan token {
|
|
bufferedInput := buffruneio.NewReader(input)
|
|
l := &sshLexer{
|
|
input: bufferedInput,
|
|
tokens: make(chan token),
|
|
line: 1,
|
|
col: 1,
|
|
endbufferLine: 1,
|
|
endbufferCol: 1,
|
|
}
|
|
go l.run()
|
|
return l.tokens
|
|
}
|