Codebase list golang-github-kevinburke-ssh-config / 37191316-6139-4d27-9d85-3e3b9c4da555/main lexer.go
37191316-6139-4d27-9d85-3e3b9c4da555/main

Tree @37191316-6139-4d27-9d85-3e3b9c4da555/main (Download .tar.gz)

lexer.go @37191316-6139-4d27-9d85-3e3b9c4da555/mainraw · history · blame

package ssh_config

import (
	"bytes"
)

// Define state functions
type sshLexStateFn func() sshLexStateFn

type sshLexer struct {
	inputIdx int
	input    []rune // Textual source

	buffer        []rune // Runes composing the current token
	tokens        chan token
	line          int
	col           int
	endbufferLine int
	endbufferCol  int
}

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 := s.peek()
	if r == '\n' {
		s.endbufferLine++
		s.endbufferCol = 1
	} else {
		s.endbufferCol++
	}
	s.inputIdx++
	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 {
	if s.inputIdx >= len(s.input) {
		return eof
	}

	r := s.input[s.inputIdx]
	return r
}

func (s *sshLexer) follow(next string) bool {
	inputIdx := s.inputIdx
	for _, expectedRune := range next {
		if inputIdx >= len(s.input) {
			return false
		}
		r := s.input[inputIdx]
		inputIdx++
		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 []byte) chan token {
	runes := bytes.Runes(input)
	l := &sshLexer{
		input:         runes,
		tokens:        make(chan token),
		line:          1,
		col:           1,
		endbufferLine: 1,
		endbufferCol:  1,
	}
	go l.run()
	return l.tokens
}