Class: IOSParser::PureLexer

Inherits:
Object
  • Object
show all
Defined in:
lib/ios_parser/lexer.rb

Constant Summary collapse

LexError =
IOSParser::LexError
ROOT_TRANSITIONS =
[
  :space,
  :banner_begin,
  :certificate_begin,
  :newline,
  :comment,
  :integer,
  :quoted_string,
  :word
].freeze

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initializePureLexer

Returns a new instance of PureLexer.



9
10
11
12
13
14
15
16
17
18
19
20
# File 'lib/ios_parser/lexer.rb', line 9

def initialize
  @text    = ''
  @token   = ''
  @tokens  = []
  @indent  = 0
  @indents = [0]
  @state   = :root
  @this_char = -1
  @line = 1
  @start_of_line = 0
  @token_line = 0
end

Instance Attribute Details

#charObject

Returns the value of attribute char.



5
6
7
# File 'lib/ios_parser/lexer.rb', line 5

def char
  @char
end

#indentObject

Returns the value of attribute indent.



5
6
7
# File 'lib/ios_parser/lexer.rb', line 5

def indent
  @indent
end

#indentsObject

Returns the value of attribute indents.



5
6
7
# File 'lib/ios_parser/lexer.rb', line 5

def indents
  @indents
end

#lineObject

Returns the value of attribute line.



5
6
7
# File 'lib/ios_parser/lexer.rb', line 5

def line
  @line
end

#start_of_lineObject

Returns the value of attribute start_of_line.



5
6
7
# File 'lib/ios_parser/lexer.rb', line 5

def start_of_line
  @start_of_line
end

#stateObject

Returns the value of attribute state.



5
6
7
# File 'lib/ios_parser/lexer.rb', line 5

def state
  @state
end

#string_terminatorObject

Returns the value of attribute string_terminator.



5
6
7
# File 'lib/ios_parser/lexer.rb', line 5

def string_terminator
  @string_terminator
end

#tokenObject

Returns the value of attribute token.



5
6
7
# File 'lib/ios_parser/lexer.rb', line 5

def token
  @token
end

#token_lineObject

Returns the value of attribute token_line.



5
6
7
# File 'lib/ios_parser/lexer.rb', line 5

def token_line
  @token_line
end

#tokensObject

Returns the value of attribute tokens.



5
6
7
# File 'lib/ios_parser/lexer.rb', line 5

def tokens
  @tokens
end

Instance Method Details



122
123
124
125
126
127
128
129
130
131
132
# File 'lib/ios_parser/lexer.rb', line 122

def banner
  self.token_line += 1 if newline?

  if banner_end_char?
    banner_end_char
  elsif banner_end_string?
    banner_end_string
  else
    token << char
  end
end


104
105
106
107
108
109
110
111
112
113
# File 'lib/ios_parser/lexer.rb', line 104

def banner_begin
  self.state = :banner
  self.token_line = 0
  tokens << make_token(:BANNER_BEGIN)
  @token_start = @this_char + 2
  @banner_delimiter = char == "\n" ? 'EOF' : char
  return unless @text[@this_char + 1] == "\n"
  self.token_line -= 1
  self.line += 1
end

Returns:

  • (Boolean)


115
116
117
118
119
120
# File 'lib/ios_parser/lexer.rb', line 115

def banner_begin?
  tokens[-2] && (
    tokens[-2].value == 'banner' ||
    tokens[-2..-1].map(&:value) == %w[authentication banner]
  )
end


148
149
150
151
152
153
154
155
156
# File 'lib/ios_parser/lexer.rb', line 148

def banner_end_char
  self.state = :root
  banner_end_clean_token
  tokens << make_token(token)
  self.line += token_line
  find_start_of_line
  tokens << make_token(:BANNER_END)
  self.token = ''
end

Returns:

  • (Boolean)


158
159
160
161
# File 'lib/ios_parser/lexer.rb', line 158

def banner_end_char?
  char == @banner_delimiter && (@text[@this_char - 1] == "\n" ||
                                @text[@this_char + 1] == "\n")
end


163
164
165
166
# File 'lib/ios_parser/lexer.rb', line 163

def banner_end_clean_token
  token.slice!(0) if token[0] == 'C'
  token.slice!(0) if ["\n", ' '].include?(token[0])
end


134
135
136
137
138
139
140
141
142
# File 'lib/ios_parser/lexer.rb', line 134

def banner_end_string
  self.state = :root
  token.chomp!(@banner_delimiter[0..-2])
  tokens << make_token(token)
  self.line += token_line
  find_start_of_line
  tokens << make_token(:BANNER_END)
  self.token = ''
end

Returns:

  • (Boolean)


144
145
146
# File 'lib/ios_parser/lexer.rb', line 144

def banner_end_string?
  @banner_delimiter.size > 1 && (token + char).end_with?(@banner_delimiter)
end

Returns:

  • (Boolean)


175
176
177
# File 'lib/ios_parser/lexer.rb', line 175

def banner_garbage?(pos)
  tokens[pos].value == :BANNER_END && tokens[pos + 1].value == 'C'
end

#call(input_text) ⇒ Object



22
23
24
25
26
27
28
29
30
31
32
# File 'lib/ios_parser/lexer.rb', line 22

def call(input_text)
  @text = input_text

  input_text.each_char.with_index do |c, i|
    @this_char = i
    self.char = c
    send(state)
  end

  finalize
end

#certificateObject



192
193
194
195
196
197
198
199
# File 'lib/ios_parser/lexer.rb', line 192

def certificate
  if token.end_with?("quit\n")
    certificate_end
  else
    self.token_line += 1 if char == "\n"
    token << char
  end
end

#certificate_beginObject



184
185
186
187
188
189
190
# File 'lib/ios_parser/lexer.rb', line 184

def certificate_begin
  self.state = :certificate
  indents.pop
  tokens[-2..-1] = [make_token(:CERTIFICATE_BEGIN, pos: tokens[-1].pos)]
  self.token_line = 0
  certificate
end

#certificate_begin?Boolean

Returns:

  • (Boolean)


179
180
181
182
# File 'lib/ios_parser/lexer.rb', line 179

def certificate_begin?
  tokens[-6] && tokens[-6].value == :INDENT &&
    tokens[-5] && tokens[-5].value == 'certificate'
end

#certificate_endObject



201
202
203
204
205
206
207
208
209
210
211
212
# File 'lib/ios_parser/lexer.rb', line 201

def certificate_end
  tokens.concat certificate_end_tokens
  self.line += 1
  update_indentation
  @token_start = @this_char

  @token = ''
  self.state = :line_start
  self.indent = 0
  self.line += 1
  root
end

#certificate_end_tokensObject

rubocop: disable AbcSize



215
216
217
218
219
220
221
222
223
224
225
# File 'lib/ios_parser/lexer.rb', line 215

def certificate_end_tokens
  cluster = []
  cluster << make_token(certificate_token_value, pos: tokens[-1].pos)
  self.line += self.token_line - 1
  cluster << make_token(:CERTIFICATE_END, pos: @this_char, col: 1)
  find_start_of_line(from: @this_char - 2)
  cluster << make_token(:EOL,
                        pos: @this_char,
                        col: @this_char - start_of_line)
  cluster
end

#certificate_token_valueObject

rubocop: enable AbcSize



228
229
230
# File 'lib/ios_parser/lexer.rb', line 228

def certificate_token_value
  token[0..-6].gsub!(/\s+/, ' ').strip
end

#commentObject



81
82
83
84
85
86
# File 'lib/ios_parser/lexer.rb', line 81

def comment
  self.state = :comment
  tokens << make_token(:EOL) if tokens.last &&
                                !tokens.last.value.is_a?(Symbol)
  comment_newline if newline?
end

#comment?Boolean

Returns:

  • (Boolean)


96
97
98
# File 'lib/ios_parser/lexer.rb', line 96

def comment?
  char == '!'
end

#comment_newlineObject



88
89
90
91
92
93
94
# File 'lib/ios_parser/lexer.rb', line 88

def comment_newline
  delimit
  self.start_of_line = @this_char + 1
  self.state = :line_start
  self.indent = 0
  self.line += 1
end

#decimalObject



254
255
256
257
258
259
260
261
# File 'lib/ios_parser/lexer.rb', line 254

def decimal
  self.state = :decimal
  if digit? then token << char
  elsif dot?   then token << char
  elsif word?  then word
  else root
  end
end

#decimal?Boolean

Returns:

  • (Boolean)


271
272
273
# File 'lib/ios_parser/lexer.rb', line 271

def decimal?
  dot? || digit?
end

#decimal_tokenObject



263
264
265
266
267
268
269
# File 'lib/ios_parser/lexer.rb', line 263

def decimal_token
  if token.count('.') > 1 || token[-1] == '.'
    word_token
  else
    make_token(Float(token))
  end
end

#delimitObject



344
345
346
347
348
349
350
351
352
353
354
355
356
# File 'lib/ios_parser/lexer.rb', line 344

def delimit
  return if token.empty?

  unless respond_to?(:"#{state}_token")
    pos = @token_start || @this_char
    raise LexError, "Unterminated #{state} starting at #{pos}: "\
                    "#{@text[pos..pos + 20].inspect}"
  end

  tokens << send(:"#{state}_token")
  self.state = :root
  self.token = ''
end

#digit?Boolean Also known as: integer?

Returns:

  • (Boolean)


245
246
247
# File 'lib/ios_parser/lexer.rb', line 245

def digit?
  ('0'..'9').cover? char
end

#dot?Boolean

Returns:

  • (Boolean)


250
251
252
# File 'lib/ios_parser/lexer.rb', line 250

def dot?
  char == '.'
end

#finalizeObject



381
382
383
384
385
386
387
388
389
390
391
392
393
# File 'lib/ios_parser/lexer.rb', line 381

def finalize
  if state == :quoted_string
    pos = @text.rindex(string_terminator)
    raise LexError, "Unterminated quoted string starting at #{pos}: "\
                    "#{@text[pos..pos + 20]}"
  end

  delimit
  self.line -= 1
  update_indentation
  scrub_banner_garbage
  tokens
end

#find_start_of_line(from: @this_char) ⇒ Object



70
71
72
73
74
75
76
77
78
79
# File 'lib/ios_parser/lexer.rb', line 70

def find_start_of_line(from: @this_char)
  from.downto(0) do |pos|
    if @text[pos] == "\n"
      self.start_of_line = pos + 1
      return start_of_line
    end
  end

  self.line_start = 0
end

#integerObject



232
233
234
235
236
237
238
239
# File 'lib/ios_parser/lexer.rb', line 232

def integer
  self.state = :integer
  if dot?   then decimal
  elsif digit? then token << char
  elsif word?  then word
  else root
  end
end

#integer_tokenObject



241
242
243
# File 'lib/ios_parser/lexer.rb', line 241

def integer_token
  token[0] == '0' ? word_token : make_token(Integer(token))
end

#lead_comment?Boolean

Returns:

  • (Boolean)


100
101
102
# File 'lib/ios_parser/lexer.rb', line 100

def lead_comment?
  char == '#' || char == '!'
end

#line_startObject



335
336
337
338
339
340
341
342
# File 'lib/ios_parser/lexer.rb', line 335

def line_start
  if space?
    self.indent += 1
  else
    update_indentation
    root_line_start
  end
end

#make_token(value, pos: nil, col: nil) ⇒ Object



63
64
65
66
67
68
# File 'lib/ios_parser/lexer.rb', line 63

def make_token(value, pos: nil, col: nil)
  pos ||= @token_start || @this_char
  col ||= pos - start_of_line + 1
  @token_start = nil
  Token.new(value, pos, line, col)
end

#newlineObject



321
322
323
324
325
326
327
328
329
# File 'lib/ios_parser/lexer.rb', line 321

def newline
  delimit
  return banner_begin if banner_begin?
  self.state = :line_start
  self.indent = 0
  tokens << make_token(:EOL)
  self.start_of_line = @this_char + 1
  self.line += 1
end

#newline?Boolean

Returns:

  • (Boolean)


331
332
333
# File 'lib/ios_parser/lexer.rb', line 331

def newline?
  char == "\n"
end

#pop_dedentObject



364
365
366
367
368
369
370
371
372
373
374
# File 'lib/ios_parser/lexer.rb', line 364

def pop_dedent
  col =
    if tokens.last.line == line
      tokens.last.col
    else
      1
    end

  tokens << make_token(:DEDENT, col: col)
  indents.pop
end

#push_indentObject



376
377
378
379
# File 'lib/ios_parser/lexer.rb', line 376

def push_indent
  tokens << make_token(:INDENT)
  indents.push(indent)
end

#quoted_stringObject



303
304
305
306
307
308
309
310
311
# File 'lib/ios_parser/lexer.rb', line 303

def quoted_string
  self.state = :quoted_string
  token << char
  if string_terminator.nil?
    self.string_terminator = char
  elsif char == string_terminator
    delimit
  end
end

#quoted_string?Boolean

Returns:

  • (Boolean)


317
318
319
# File 'lib/ios_parser/lexer.rb', line 317

def quoted_string?
  char == '"' || char == "'"
end

#quoted_string_tokenObject



313
314
315
# File 'lib/ios_parser/lexer.rb', line 313

def quoted_string_token
  make_token(token)
end

#rootObject

Raises:



45
46
47
48
49
50
51
52
53
# File 'lib/ios_parser/lexer.rb', line 45

def root
  @token_start ||= @this_char

  ROOT_TRANSITIONS.each do |meth|
    return send(meth) if send(:"#{meth}?")
  end

  raise LexError, "Unknown character #{char.inspect}"
end

#root_line_startObject



55
56
57
58
59
60
61
# File 'lib/ios_parser/lexer.rb', line 55

def root_line_start
  if lead_comment?
    comment
  else
    root
  end
end

#scrub_banner_garbageObject



168
169
170
171
172
173
# File 'lib/ios_parser/lexer.rb', line 168

def scrub_banner_garbage
  tokens.each_index do |i|
    next unless tokens[i + 1]
    tokens.slice!(i + 1) if banner_garbage?(i)
  end
end

#spaceObject



294
295
296
297
# File 'lib/ios_parser/lexer.rb', line 294

def space
  delimit
  self.indent += 1 if tokens.last && tokens.last.value == :EOL
end

#space?Boolean

Returns:

  • (Boolean)


299
300
301
# File 'lib/ios_parser/lexer.rb', line 299

def space?
  char == ' ' || char == "\t" || char == "\r"
end

#update_indentationObject



358
359
360
361
362
# File 'lib/ios_parser/lexer.rb', line 358

def update_indentation
  pop_dedent while 1 < indents.size && indent <= indents[-2]
  push_indent if indent > indents.last
  self.indent = 0
end

#wordObject



275
276
277
278
# File 'lib/ios_parser/lexer.rb', line 275

def word
  self.state = :word
  word? ? token << char : root
end

#word?Boolean

Returns:

  • (Boolean)


284
285
286
287
288
289
290
291
292
# File 'lib/ios_parser/lexer.rb', line 284

def word?
  digit? || dot? ||
    ('a'..'z').cover?(char) ||
    ('A'..'Z').cover?(char) ||
    ['-', '+', '$', ':', '/', ',', '(', ')', '|', '*', '#', '=', '<', '>',
     '!', '"', '&', '@', ';', '%', '~', '{', '}', "'", '?', '[', ']', '_',
     '^', '\\', '`'].include?(char) ||
    /[[:graph:]]/.match(char)
end

#word_tokenObject



280
281
282
# File 'lib/ios_parser/lexer.rb', line 280

def word_token
  make_token(token)
end