main
1package ssh_config
2
3import (
4 "bytes"
5)
6
7// Define state functions
8type sshLexStateFn func() sshLexStateFn
9
10type sshLexer struct {
11 inputIdx int
12 input []rune // Textual source
13
14 buffer []rune // Runes composing the current token
15 tokens chan token
16 line int
17 col int
18 endbufferLine int
19 endbufferCol int
20}
21
22func (s *sshLexer) lexComment(previousState sshLexStateFn) sshLexStateFn {
23 return func() sshLexStateFn {
24 growingString := ""
25 for next := s.peek(); next != '\n' && next != eof; next = s.peek() {
26 if next == '\r' && s.follow("\r\n") {
27 break
28 }
29 growingString += string(next)
30 s.next()
31 }
32 s.emitWithValue(tokenComment, growingString)
33 s.skip()
34 return previousState
35 }
36}
37
38// lex the space after an equals sign in a function
39func (s *sshLexer) lexRspace() sshLexStateFn {
40 for {
41 next := s.peek()
42 if !isSpace(next) {
43 break
44 }
45 s.skip()
46 }
47 return s.lexRvalue
48}
49
50func (s *sshLexer) lexEquals() sshLexStateFn {
51 for {
52 next := s.peek()
53 if next == '=' {
54 s.emit(tokenEquals)
55 s.skip()
56 return s.lexRspace
57 }
58 // TODO error handling here; newline eof etc.
59 if !isSpace(next) {
60 break
61 }
62 s.skip()
63 }
64 return s.lexRvalue
65}
66
67func (s *sshLexer) lexKey() sshLexStateFn {
68 growingString := ""
69
70 for r := s.peek(); isKeyChar(r); r = s.peek() {
71 // simplified a lot here
72 if isSpace(r) || r == '=' {
73 s.emitWithValue(tokenKey, growingString)
74 s.skip()
75 return s.lexEquals
76 }
77 growingString += string(r)
78 s.next()
79 }
80 s.emitWithValue(tokenKey, growingString)
81 return s.lexEquals
82}
83
84func (s *sshLexer) lexRvalue() sshLexStateFn {
85 growingString := ""
86 for {
87 next := s.peek()
88 switch next {
89 case '\r':
90 if s.follow("\r\n") {
91 s.emitWithValue(tokenString, growingString)
92 s.skip()
93 return s.lexVoid
94 }
95 case '\n':
96 s.emitWithValue(tokenString, growingString)
97 s.skip()
98 return s.lexVoid
99 case '#':
100 s.emitWithValue(tokenString, growingString)
101 s.skip()
102 return s.lexComment(s.lexVoid)
103 case eof:
104 s.next()
105 }
106 if next == eof {
107 break
108 }
109 growingString += string(next)
110 s.next()
111 }
112 s.emit(tokenEOF)
113 return nil
114}
115
116func (s *sshLexer) read() rune {
117 r := s.peek()
118 if r == '\n' {
119 s.endbufferLine++
120 s.endbufferCol = 1
121 } else {
122 s.endbufferCol++
123 }
124 s.inputIdx++
125 return r
126}
127
128func (s *sshLexer) next() rune {
129 r := s.read()
130
131 if r != eof {
132 s.buffer = append(s.buffer, r)
133 }
134 return r
135}
136
137func (s *sshLexer) lexVoid() sshLexStateFn {
138 for {
139 next := s.peek()
140 switch next {
141 case '#':
142 s.skip()
143 return s.lexComment(s.lexVoid)
144 case '\r':
145 fallthrough
146 case '\n':
147 s.emit(tokenEmptyLine)
148 s.skip()
149 continue
150 }
151
152 if isSpace(next) {
153 s.skip()
154 }
155
156 if isKeyStartChar(next) {
157 return s.lexKey
158 }
159
160 // removed IsKeyStartChar and lexKey. probably will need to readd
161
162 if next == eof {
163 s.next()
164 break
165 }
166 }
167
168 s.emit(tokenEOF)
169 return nil
170}
171
172func (s *sshLexer) ignore() {
173 s.buffer = make([]rune, 0)
174 s.line = s.endbufferLine
175 s.col = s.endbufferCol
176}
177
178func (s *sshLexer) skip() {
179 s.next()
180 s.ignore()
181}
182
183func (s *sshLexer) emit(t tokenType) {
184 s.emitWithValue(t, string(s.buffer))
185}
186
187func (s *sshLexer) emitWithValue(t tokenType, value string) {
188 tok := token{
189 Position: Position{s.line, s.col},
190 typ: t,
191 val: value,
192 }
193 s.tokens <- tok
194 s.ignore()
195}
196
197func (s *sshLexer) peek() rune {
198 if s.inputIdx >= len(s.input) {
199 return eof
200 }
201
202 r := s.input[s.inputIdx]
203 return r
204}
205
206func (s *sshLexer) follow(next string) bool {
207 inputIdx := s.inputIdx
208 for _, expectedRune := range next {
209 if inputIdx >= len(s.input) {
210 return false
211 }
212 r := s.input[inputIdx]
213 inputIdx++
214 if expectedRune != r {
215 return false
216 }
217 }
218 return true
219}
220
221func (s *sshLexer) run() {
222 for state := s.lexVoid; state != nil; {
223 state = state()
224 }
225 close(s.tokens)
226}
227
228func lexSSH(input []byte) chan token {
229 runes := bytes.Runes(input)
230 l := &sshLexer{
231 input: runes,
232 tokens: make(chan token),
233 line: 1,
234 col: 1,
235 endbufferLine: 1,
236 endbufferCol: 1,
237 }
238 go l.run()
239 return l.tokens
240}