| 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 |
| } |