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