iparser 1.1.6 → 1.1.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,490 @@
1
+ module Iparser
2
+ # Used for create parser machine.
3
+ class Machine
4
+ INITIAL_STATE = 'wait'
5
+
6
+ attr_reader :parserstate
7
+
8
+ # call-seq:
9
+ # Machine.new( )
10
+ def initialize ( )
11
+ @buffer = [] # буфер для символов входного потока
12
+ @states = [] # массив с состояниями парсера, <ParserState> объекты
13
+ @chain = [] # цепочка работающих состояний
14
+ #
15
+ # Машина состояний для метода classify.
16
+ #
17
+ @matchstate = {
18
+ :state => 0,
19
+ :index => 0
20
+ }
21
+ @parserstate = ''
22
+ end
23
+
24
+ # Сбрасывает чувствительные переменные.
25
+ def reset ( ) # :nodoc:
26
+ @buffer = []
27
+ @states.each do |s|
28
+ s.ientry = 0
29
+ s.ileave = 0
30
+ end
31
+ end
32
+
33
+ # Return current state name.
34
+ def current_state ( )
35
+ return @chain.last.statename if @chain.size > 0
36
+ return nil
37
+ end
38
+
39
+ # Initialize parser object,
40
+ # should be called before call other methods.
41
+ def prestart ( )
42
+ reset( )
43
+ @matchstate[:state] = 0
44
+ @chain = [ @states[0], ]
45
+ end
46
+
47
+ # Display information about of each state of parser.
48
+ def display ( )
49
+ puts 'Parser states: ' + @states.size.to_s
50
+
51
+ @states.each do |st|
52
+ puts
53
+ puts '** state: ' + st.statename
54
+ puts 'branches: '
55
+ st.branches.each do |br|
56
+ puts ' ' + @states[br].statename
57
+ end
58
+ end
59
+ end
60
+
61
+ # Обработка ввода для интерактивных режимов работы.
62
+ def interactive_input ( ) # :nodoc:
63
+ state = 0
64
+ rv = ""
65
+ str = gets
66
+
67
+ # Сразу нажата <Enter> => exit.
68
+ return rv if str[0] == '\n'
69
+
70
+ # Выполняем разбор посимвольно.
71
+ str.each_char do |c|
72
+ break if c == ?\n
73
+ case state
74
+ #
75
+ # Сборка символов и проверка на наличие
76
+ # экранирующего символа, значит это ESC-символы.
77
+ when 0
78
+ if c == '\\' then
79
+ state = 1
80
+ else
81
+ rv += c
82
+ end
83
+ #
84
+ # Анализ ESC символа.
85
+ when 1
86
+ case c
87
+ when '0'
88
+ rv += "\0"
89
+ when 'n'
90
+ rv += "\n"
91
+ when 'r'
92
+ rv += "\r"
93
+ when '\\'
94
+ rv += "\\"
95
+ when 'a'
96
+ rv += "\a"
97
+ when 'b'
98
+ rv += "\b"
99
+ when 't'
100
+ rv += "\t"
101
+ when 'v'
102
+ rv += "\v"
103
+ when 'f'
104
+ rv += "\f"
105
+ else
106
+ puts "\nERROR: unrecognized esc-symbols.\n"
107
+ exit
108
+ end
109
+ state = 0
110
+ end
111
+ end
112
+ return rv
113
+ end
114
+
115
+ # Обработка вывода для интерактивных режимов работы.
116
+ def interactive_output( istr ) # :nodoc:
117
+ str = []
118
+ istr.bytes.each do |c|
119
+ case c
120
+ when 0
121
+ str << c.to_s + ":\\0"
122
+ when 10
123
+ str << c.to_s + ":\\n"
124
+ when 13
125
+ str << c.to_s + ":\\r"
126
+ when 7
127
+ str << c.to_s + ":\\a"
128
+ when 8
129
+ str << c.to_s + ":\\b"
130
+ when 9
131
+ str << c.to_s + ":\\t"
132
+ when 11
133
+ str << c.to_s + ":\\v"
134
+ when 12
135
+ str << c.to_s + ":\\f"
136
+ else
137
+ str << c.to_s + ":" + c.chr
138
+ end
139
+ end
140
+ return str
141
+ end
142
+
143
+ # Run parser machine for check in interactive mode.
144
+ def interactive_parser ( )
145
+ puts 'Press <Enter> to exit...'
146
+
147
+ # Цикл обработки ввода.
148
+ loop do
149
+ str = interactive_input( )
150
+ break if str == ""
151
+
152
+ # Цикл посимвольной классификаци.
153
+ str.bytes.each do |c|
154
+ parse( c.chr )
155
+ puts 'parser: ' + @parserstate
156
+ puts 'symbol: ' + interactive_output( c.chr ).to_s
157
+ puts 'state: ' + @chain.last.statename
158
+ puts
159
+ end
160
+ end
161
+ end
162
+
163
+ # call-seq:
164
+ # s = Parser::State.new('idle')
165
+ # p = Parser::Machine.new
166
+ # p << s
167
+ #
168
+ # Add any parser-state to current parser.
169
+ def addstate ( ps )
170
+ raise TypeError, ps.class.to_s + ': Incorrectly types for \'<<\' method of <Parser>.' unless
171
+ ps.instance_of? State
172
+ @states << ps
173
+ end
174
+
175
+ # call-seq:
176
+ # some_state1.branches << parser.state_index(some_state2).
177
+ # Return index
178
+ def state_index ( state )
179
+ raise TypeError, ps.class.to_s + ': Incorrectly types for \'state_index\' method of <Parser>.' unless
180
+ state.instance_of? State
181
+
182
+ @states.each_with_index do |st,i|
183
+ return i if state == st
184
+ end
185
+ raise "State <#{state.statename}> is not exist in Parser."
186
+ end
187
+
188
+ # Сравнивает символы входного потока
189
+ # с символами из указанного шаблона.
190
+ # В качестве шаблона выступают поля <entry> или <leave>
191
+ # объектов типа <ParserState>.
192
+ def cmp ( tmp, idx ) # :nodoc:
193
+
194
+ # проверка на случай если шаблон не задан,
195
+ # т.е. проинициализирован в [].
196
+ if tmp.size > 0 then
197
+ if idx < tmp.size then
198
+ case tmp[idx].class.to_s
199
+ when 'Regexp'
200
+ return true if @buffer.last =~ tmp[ idx ]
201
+ when 'String'
202
+ return true if @buffer.last == tmp[ idx ]
203
+ end
204
+ end
205
+ end
206
+ return false
207
+ end
208
+
209
+ # Поиск в массиве указанного диапазона,
210
+ # указанного в параметрах символа.
211
+ #
212
+ # >=0 : индекс совпавшего элемента.
213
+ # -1 : нет совпадений.
214
+ def checkback ( tmp, len ) # :nodoc:
215
+ if len > 0 then
216
+ i = len
217
+ len.times do
218
+ i = i - 1
219
+ return i if cmp( tmp, i )
220
+ end
221
+ end
222
+ return -1
223
+ end
224
+
225
+ # Находит соответствие между символами входного потока
226
+ # и возможными переходами.
227
+ #
228
+ # При совпадени возвращает индекс состояния
229
+ # в массиве состояний, иначе:
230
+ #
231
+ # >0 : прыжок в новое состояние
232
+ # -1 : возврат в предыдущее состояние
233
+ # -2 : еще идет проверка.
234
+ # -3 : нет cовпадений (промах).
235
+ def classify ( state ) # :nodoc:
236
+ case @matchstate[:state]
237
+
238
+ # Состояние еще не определено.
239
+ # :state = 0
240
+ when 0
241
+ mcount = 0
242
+ mindex = 0
243
+ backtag = 0
244
+ #
245
+ # Проверка условия выхода из состояния.
246
+ if cmp( state.leave, state.ileave ) then
247
+ state.ileave = state.ileave.next
248
+ #
249
+ # Возврат в предыдущее состояние.
250
+ if state.ileave >= state.leave.size then
251
+ return -1
252
+ end
253
+ backtag = 1
254
+ mindex = -1
255
+ else
256
+ #
257
+ # Нет совпадения, но если уже часть сравнений
258
+ # успешна, то возможно входной символ совпадает
259
+ # с предыдущими, уже совпавшими символами,
260
+ # т.е. как откат в режиме <wait>.
261
+ #
262
+ i = checkback( state.leave, state.ileave )
263
+
264
+ if i != -1 then
265
+ state.ileave = i.next
266
+ backtag = 1
267
+ else
268
+ state.ileave = 0
269
+ backtag = 0
270
+ end
271
+ end
272
+ #
273
+ # Проверка возможных переходов для
274
+ # указанного в параметрах состояния.
275
+ state.branches.each do |b|
276
+ if cmp( @states[b].entry, @states[b].ientry ) then
277
+ mcount = mcount + 1
278
+ mindex = b
279
+ @states[b].ientry = @states[b].ientry.next
280
+ #
281
+ # состояние полностью пройдено.
282
+ if @states[ b ].ientry >= @states[ b ].entry.size then
283
+ return b
284
+ end
285
+ else
286
+ #
287
+ # Нет совпадения, но если уже часть сравнений
288
+ # успешна, то возможно входной символ совпадает
289
+ # с предыдущими, уже совпавшими символами,
290
+ # т.е. как откат в режиме <wait>.
291
+ i = checkback( @states[b].entry, @states[b].ientry )
292
+
293
+ if i != -1 then
294
+ mcount = mcount + 1
295
+ mindex = b
296
+ @states[b].ientry = i.next
297
+ else
298
+ @states[b].ientry = 0
299
+ end
300
+ end
301
+ end
302
+ #
303
+ # Анализ количества совпадений.
304
+ case (mcount + backtag)
305
+ #
306
+ # нет совпадений.
307
+ when 0
308
+ return (-3 + backtag)
309
+ #
310
+ # однозначное совпадение, но весь массив шаблонов
311
+ # еще не пройден.
312
+ when 1
313
+ if mindex == -1 then
314
+ @matchstate[:state] = 2
315
+ else
316
+ @matchstate[:state] = 1
317
+ @matchstate[:index] = mindex
318
+ end
319
+ return -2
320
+ #
321
+ # нет однозначного соответствия.
322
+ else
323
+ return -2
324
+ end
325
+
326
+ # Состояние точно определено (переход в перед).
327
+ # :state = 1
328
+ when 1
329
+ i = @matchstate[:index]
330
+ if cmp( @states[ i ].entry, @states[ i ].ientry ) then
331
+ #
332
+ # Инкремент счетчика (индекса) вхождений.
333
+ @states[ i ].ientry = @states[ i ].ientry.next
334
+ #
335
+ # Массив шаблонов совпадает полностью.
336
+ # можно считать, что 100% совпадение.
337
+ if @states[ i ].ientry >= @states[ i ].entry.size then
338
+ @matchstate[:state] = 0
339
+ return i
340
+ end
341
+ return -2
342
+ end
343
+ #
344
+ # Нет совпадения, но если уже часть сравнений
345
+ # успешна, то возможно входной символ совпадает
346
+ # с предыдущими, уже совпавшими символами,
347
+ # т.е. как откат в режиме <wait>.
348
+ idx = checkback( @states[i].entry, @states[i].ientry )
349
+
350
+ if idx != -1 then
351
+ @states[i].ientry = idx.next
352
+ return -2
353
+ end
354
+ @states[i].ientry = 0
355
+ @matchstate[:state] = 0
356
+ return -3
357
+
358
+ # Состояние точно определено (возврат назад).
359
+ # :state = 2
360
+ when 2
361
+ if cmp( state.leave, state.ileave ) then
362
+ state.ileave = state.ileave.next
363
+ #
364
+ # Возврат в предыдущее состояние.
365
+ if state.ileave >= state.leave.size then
366
+ @matchstate[:state] = 0
367
+ return -1
368
+ end
369
+ return -2
370
+ end
371
+ #
372
+ # Нет совпадения, но если уже часть сравнений
373
+ # успешна, то возможно входной символ совпадает
374
+ # с предыдущими, уже совпавшими символами,
375
+ # т.е. как откат в режиме <wait>.
376
+ #
377
+ i = checkback( state.leave, state.ileave )
378
+
379
+ if i != -1 then
380
+ state.ileave = i.next
381
+ return -2
382
+ end
383
+ state.ileave = 0
384
+ @matchstate[:state] = 0
385
+ return -3
386
+
387
+ end # case @matchstate
388
+ end
389
+
390
+ # Main method, used for parse input stream.
391
+ # Parse will be starting in unit with nil index (0).
392
+ #
393
+ # Return true if parsing process is successful, else return false.
394
+ def parse ( c )
395
+ @parserstate = INITIAL_STATE
396
+ retval = true
397
+ #
398
+ # * Фиксированное состояние (определенное): -1.
399
+ # * Не фиксированное состояние (неопределенное): -2.
400
+ # * Переход (смена состояний): >0.
401
+ #
402
+ @buffer << c
403
+
404
+ # Задан шаблон для игнорирования символов.
405
+ if @chain.last.ignore[:all].size > 0 then
406
+ return retval if @chain.last.ignore[:all].include?(c)
407
+ end
408
+
409
+ # Проверка переходов в другие состояния.
410
+ r = classify( @chain.last )
411
+
412
+ # Переход (прыжок) в другое состояние.
413
+ # <branch>:
414
+ if r >= 0 then
415
+ @chain << @states[r]
416
+ if @chain.last.run_init( @buffer ) == nil then
417
+ reset( )
418
+ @parserstate = 'branch'
419
+ else
420
+ @parserstate = 'error'
421
+ retval = false
422
+ end
423
+
424
+ # Возврат из текущего состояния.
425
+ # <back>:
426
+ elsif r == -1 then
427
+ if @chain.last.run_fini( @buffer ) == nil then
428
+ #
429
+ # если это состояние не первое в цепочке
430
+ # тогда откатываемся назад.
431
+ if @chain.size > 1 then
432
+ @chain.delete_at( @chain.size - 1 )
433
+ end
434
+ reset( )
435
+ @parserstate = 'back'
436
+ else
437
+ @parserstate = 'error'
438
+ retval = false
439
+ end
440
+
441
+ # Нет совпадений.
442
+ # <miss>:
443
+ elsif r == -3 then
444
+ #
445
+ # если в процессе состояния <wait>
446
+ # мы попали в <miss>, то накопленный
447
+ # буфер надо обработать.
448
+ @buffer.each do |ch|
449
+ @parserstate = 'miss'
450
+ tag = true
451
+ if @chain.last.ignore[:handler].size > 0 then
452
+ tag = false if @chain.last.ignore[:handler].include?(ch)
453
+ end
454
+ if tag == true then
455
+ r = @chain.last.run_handler( ch )
456
+ #
457
+ # Анализ результата обработки состояния.
458
+ case r.class.to_s
459
+ #
460
+ # Fixnum - переход на любое состояние (индекс).
461
+ when 'Fixnum'
462
+ if( (r >= 0) && (r < @states.size) ) then
463
+ @chain << @states[r]
464
+ reset( )
465
+ @parserstate = 'hardset'
466
+ else
467
+ raise TypeError, "Method <#{@chain.last.statename}> return incorrectly index."
468
+ end
469
+ #
470
+ # nil - ничего не возвращает.
471
+ when 'NilClass'
472
+ #
473
+ # else - расценивается как ошибка обработки.
474
+ # обработка ложится на плечи разработчика.
475
+ else
476
+ @parserstate = 'error'
477
+ retval = false
478
+ break
479
+ end
480
+ end
481
+ end
482
+ @buffer = []
483
+ end
484
+ return retval
485
+ end
486
+
487
+ private :reset, :cmp, :checkback, :interactive_input, :interactive_output
488
+ end # class Machine
489
+
490
+ end