iparser 1.1.3 → 1.1.4
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 +4 -4
- data/README.md +120 -178
- data/iparser.gemspec +2 -2
- data/lib/iparser.rb +79 -124
- data/lib/iparser/version.rb +1 -1
- metadata +5 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9a12b304b0cfd0fc94f9043f03321648f95f2a60
|
4
|
+
data.tar.gz: 48f5b42bf26ec9cc78ba8ec443085325693a9173
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c0d50e2ccdd4cc0393a302869fffe3d10ece69ffb828f761fda020c06bbf5edd56a8aa3ea0e57ca4b3079b077b38b38e9058451a59e4fa1e0e2c499b56692bbf
|
7
|
+
data.tar.gz: 42e50dd5f5a159452909a9aeaf52428c203fac762872221d2a308fcd273bd7313ca4a00f855b9611784b6b3e9f91829607c855f74bec6ea710ae5ea45a8a9ff9
|
data/README.md
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# Iparser
|
2
2
|
|
3
|
-
Universal parser machine to generate your specific parsers.
|
3
|
+
Universal parser machine to generate your specific parsers (Parser engine).
|
4
4
|
Used for simple and fast create your specific parsers.
|
5
5
|
|
6
6
|
## Installation
|
@@ -22,249 +22,174 @@ Or install it yourself as:
|
|
22
22
|
## Usage
|
23
23
|
|
24
24
|
For example usage, present here very simple parser for automatically generate documentation from source code.
|
25
|
+
Create file 'parser_example.rb' and copy the contents from the file *source № 1*.
|
25
26
|
|
26
27
|
*source № 1*:
|
27
28
|
```ruby
|
28
29
|
require 'iparser'
|
29
30
|
|
30
|
-
#
|
31
31
|
# Create parser-machine object.
|
32
|
-
#
|
33
32
|
parser = Iparser::Machine.new
|
34
|
-
|
35
|
-
#
|
36
33
|
# Create startup state for this parser-machine.
|
37
|
-
#
|
38
34
|
ps_idle = Iparser::State.new('idle')
|
39
|
-
|
40
|
-
#
|
41
|
-
# Add branch indexes to 'comment-line' and 'comment-block' state.
|
42
|
-
#
|
43
|
-
ps_idle.branches << 1
|
44
|
-
ps_idle.branches << 2
|
45
|
-
|
46
|
-
#
|
47
35
|
# Create single line comment state for this parser-machine.
|
48
|
-
#
|
49
36
|
ps_cline = Iparser::State.new('comment-line')
|
50
|
-
ps_cline.entry << /\//
|
51
|
-
ps_cline.entry << /\//
|
52
|
-
ps_cline.entry << /\//
|
53
|
-
ps_cline.leave << /[\n\r]/
|
54
|
-
|
55
|
-
#
|
56
37
|
# Create multiline comment state for this parser-machine.
|
57
|
-
#
|
58
38
|
ps_cblock = Iparser::State.new('comment-block')
|
59
|
-
ps_cblock.entry << /\//
|
60
|
-
ps_cblock.entry << /\*/
|
61
|
-
ps_cblock.entry << /\*/
|
62
|
-
ps_cblock.leave << /\*/
|
63
|
-
ps_cblock.leave << /\//
|
64
|
-
ps_cblock.ignore << '*'
|
65
39
|
|
66
|
-
#
|
67
40
|
# Add all states to parser-machine.
|
68
|
-
#
|
69
41
|
parser.addstate ps_idle
|
70
42
|
parser.addstate ps_cline
|
71
43
|
parser.addstate ps_cblock
|
72
44
|
|
73
|
-
#
|
45
|
+
# Describe 'comment-line' state.
|
46
|
+
# Set template for entry this state (String or Regexp).
|
47
|
+
ps_cline.entry << '/'
|
48
|
+
ps_cline.entry << '/'
|
49
|
+
ps_cline.entry << '/'
|
50
|
+
# Set template for leave this state (String or Regexp).
|
51
|
+
ps_cline.leave << /[\n\r]/
|
52
|
+
# Add handler to 'commaent-line' state.
|
53
|
+
ps_cline.init( method(:doc_init) )
|
54
|
+
ps_cline.handler( method(:doc_handler) )
|
55
|
+
ps_cline.fini( method(:doc_fini) )
|
56
|
+
# Add branch indexes to 'comment-line' and 'comment-block' state.
|
57
|
+
# From 'idle' we can branch to 'comment-line' or 'comment-block' states.
|
58
|
+
ps_idle.branches << parser.state_index( ps_cline )
|
59
|
+
ps_idle.branches << parser.state_index( ps_cblock )
|
60
|
+
|
61
|
+
# Describe 'comment-block' state.
|
62
|
+
# Set template for entry this state (String or Regexp).
|
63
|
+
ps_cblock.entry << '/'
|
64
|
+
ps_cblock.entry << '*'
|
65
|
+
ps_cblock.entry << '*'
|
66
|
+
# Set template for leave this state (String or Regexp).
|
67
|
+
ps_cblock.leave << '*'
|
68
|
+
ps_cblock.leave << '/'
|
69
|
+
# Set template for ignore this state (String format only).
|
70
|
+
ps_cblock.ignore << '*'
|
71
|
+
|
74
72
|
# Call parser startup method.
|
75
|
-
#
|
76
73
|
parser.prestart
|
77
|
-
|
78
|
-
#
|
79
74
|
# Call interactive mode for check state-machine.
|
80
|
-
#
|
81
75
|
parser.interactive_parser
|
82
76
|
```
|
83
77
|
|
84
|
-
Run this script and typing `'///'` for branch to 'comment-line' state.
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
78
|
+
Run this script `ruby parser_example.rb` and typing `'///'` for branch to 'comment-line' state.
|
79
|
+
Then type `'\n'` or `'\r'` for leave this state.
|
80
|
+
Press `enter` (input empty string) to leave interactive mode.
|
81
|
+
Check each state.
|
82
|
+
|
83
|
+
**NOTE**: Type `'\\'` for input `'\'`.
|
84
|
+
|
85
|
+
Each state has the following templates:
|
86
|
+
1 __entry__ - used for set condition (template) to entry state.
|
87
|
+
2 __leave__ - used for set condition (template) to leave state (return to previous state).
|
88
|
+
3 __ignore__ - used for set symbols to ignoring.
|
89
|
+
|
90
|
+
After successfully check, you can add handler to each state.
|
91
|
+
Each state has the following handlers:
|
92
|
+
1 __init__ - is state contructor (called when entry to state).
|
93
|
+
2 __fini__ - is state destructor (called when leave state).
|
94
|
+
3 __handler__ - is state handler (called every time, is still in state).
|
95
|
+
|
96
|
+
Each handler can return the following values: __nil__ - nothing is done (method `.parse` return `true`)
|
97
|
+
and __any__ values for break parsing (method `.parse` return `false`). For break parsing process you
|
98
|
+
should check return value of `.parse` method. For example:
|
89
99
|
```ruby
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
if( ARGV.size != 1 || !File.exist?(ARGV[0]) )
|
94
|
-
puts
|
95
|
-
puts "ERROR: unable to open file #{ARGV[0]}"
|
96
|
-
puts
|
97
|
-
exit
|
98
|
-
end
|
99
|
-
#
|
100
|
-
# Create output file.
|
101
|
-
#
|
102
|
-
$fout = File.new( 'index.html', 'w' )
|
103
|
-
|
104
|
-
#
|
105
|
-
# Create initializer method for parser-states.
|
106
|
-
#
|
107
|
-
def doc_init ( str )
|
108
|
-
$fout.print "<p>"
|
109
|
-
end
|
110
|
-
#
|
111
|
-
# Create handler method for parser-states.
|
112
|
-
#
|
113
|
-
def doc_handler ( c )
|
114
|
-
$fout.print c
|
115
|
-
end
|
116
|
-
#
|
117
|
-
# Create finalizer method for parser-states.
|
118
|
-
#
|
119
|
-
def doc_fini ( str )
|
120
|
-
$fout.puts "</p>"
|
121
|
-
end
|
122
|
-
```
|
123
|
-
Method `doc_init` is state contructor.
|
124
|
-
Method `doc_handler` is state handler and call in `comment-line` or `comment-block` for each input char.
|
125
|
-
Method `doc_fini` is state destructor.
|
126
|
-
|
127
|
-
Handler may be return following data types: `Fixnum` - index to branch (>=0),
|
128
|
-
`NilClass` - hold current state (nil) and any data types for break parse (error, method `parse`
|
129
|
-
return `false`).
|
130
|
-
|
131
|
-
For `comment-block` state set ignore char - `*`, and handler don't called to this chars.
|
132
|
-
|
133
|
-
Add the following code instead `parser.interactive_parser`:
|
134
|
-
|
135
|
-
*source № 3*:
|
136
|
-
```ruby
|
137
|
-
$fout.puts "<html>"
|
138
|
-
$fout.puts "<body>"
|
139
|
-
|
140
|
-
File.open( ARGV[0], 'r' ).each do |line|
|
141
|
-
line.each_char do |c|
|
142
|
-
parser.parse(c)
|
143
|
-
end
|
100
|
+
parser = Iparser::Machine.new
|
101
|
+
'123asd'.each_char do |c|
|
102
|
+
exit if !parser.parse(c)
|
144
103
|
end
|
145
|
-
|
146
|
-
$fout.puts "</body>"
|
147
|
-
$fout.puts "</html>"
|
148
|
-
$fout.close
|
149
104
|
```
|
105
|
+
Also each state have a `branches` field. Use the `branches` to add the index state,
|
106
|
+
which is a possible jump.
|
150
107
|
|
151
|
-
|
152
|
-
|
153
|
-
*source № 4*:
|
154
|
-
```ruby
|
155
|
-
#
|
156
|
-
# Add handlers for states.
|
157
|
-
#
|
158
|
-
ps_cline.init( method(:doc_init) )
|
159
|
-
ps_cline.handler( method(:doc_handler) )
|
160
|
-
ps_cline.fini( method(:doc_fini) )
|
108
|
+
We create the following handlers:
|
161
109
|
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
```
|
110
|
+
* Method `doc_init` is state destructor.
|
111
|
+
* Method `doc_handler` is state handler and call in `comment-line` or `comment-block` for each input char.
|
112
|
+
* Method `doc_fini` is state destructor.
|
166
113
|
|
167
|
-
|
114
|
+
For `comment-block` state set ignore char - `*`, and handler don't called to this chars.
|
115
|
+
The result is a file with the following content of 'parser_example.rb':
|
168
116
|
|
169
|
-
*source №
|
117
|
+
*source № 2*:
|
170
118
|
```ruby
|
171
119
|
require 'iparser'
|
172
120
|
|
173
|
-
#
|
174
121
|
# Simple check startup arguments.
|
175
|
-
#
|
176
122
|
if( ARGV.size != 1 || !File.exist?(ARGV[0]) )
|
177
123
|
puts
|
178
124
|
puts "ERROR: unable to open file #{ARGV[0]}"
|
179
125
|
puts
|
180
126
|
exit
|
181
127
|
end
|
182
|
-
|
128
|
+
|
183
129
|
# Create output file.
|
184
|
-
#
|
185
130
|
$fout = File.new( 'index.html', 'w' )
|
186
131
|
|
187
|
-
#
|
188
132
|
# Create initializer method for parser-states.
|
189
|
-
#
|
190
133
|
def doc_init ( str )
|
191
134
|
$fout.print "<p>"
|
192
135
|
end
|
193
|
-
#
|
194
136
|
# Create handler method for parser-states.
|
195
|
-
#
|
196
137
|
def doc_handler ( c )
|
197
138
|
$fout.print c
|
198
139
|
end
|
199
|
-
#
|
200
140
|
# Create finalizer method for parser-states.
|
201
|
-
#
|
202
141
|
def doc_fini ( str )
|
203
142
|
$fout.puts "</p>"
|
204
143
|
end
|
205
144
|
|
206
|
-
#
|
207
145
|
# Create parser-machine object.
|
208
|
-
#
|
209
146
|
parser = Iparser::Machine.new
|
210
|
-
|
211
|
-
#
|
212
147
|
# Create startup state for this parser-machine.
|
213
|
-
#
|
214
148
|
ps_idle = Iparser::State.new('idle')
|
215
|
-
|
216
|
-
#
|
217
|
-
# Add branch indexes to 'comment-line' and 'comment-block' state.
|
218
|
-
#
|
219
|
-
ps_idle.branches << 1
|
220
|
-
ps_idle.branches << 2
|
221
|
-
|
222
|
-
#
|
223
149
|
# Create single line comment state for this parser-machine.
|
224
|
-
#
|
225
150
|
ps_cline = Iparser::State.new('comment-line')
|
226
|
-
ps_cline.entry << /\//
|
227
|
-
ps_cline.entry << /\//
|
228
|
-
ps_cline.entry << /\//
|
229
|
-
ps_cline.leave << /[\n\r]/
|
230
|
-
|
231
|
-
#
|
232
151
|
# Create multiline comment state for this parser-machine.
|
233
|
-
#
|
234
152
|
ps_cblock = Iparser::State.new('comment-block')
|
235
|
-
ps_cblock.entry << /\//
|
236
|
-
ps_cblock.entry << /\*/
|
237
|
-
ps_cblock.entry << /\*/
|
238
|
-
ps_cblock.leave << /\*/
|
239
|
-
ps_cblock.leave << /\//
|
240
|
-
ps_cblock.ignore << '*'
|
241
153
|
|
242
|
-
#
|
243
|
-
|
244
|
-
|
154
|
+
# Add all states to parser-machine.
|
155
|
+
parser.addstate ps_idle
|
156
|
+
parser.addstate ps_cline
|
157
|
+
parser.addstate ps_cblock
|
158
|
+
|
159
|
+
# Describe 'comment-line' state.
|
160
|
+
# Set template for entry this state (String or Regexp).
|
161
|
+
ps_cline.entry << '/'
|
162
|
+
ps_cline.entry << '/'
|
163
|
+
ps_cline.entry << '/'
|
164
|
+
# Set template for leave this state (String or Regexp).
|
165
|
+
ps_cline.leave << /[\n\r]/
|
166
|
+
# Add handler to 'commaent-line' state.
|
245
167
|
ps_cline.init( method(:doc_init) )
|
246
168
|
ps_cline.handler( method(:doc_handler) )
|
247
169
|
ps_cline.fini( method(:doc_fini) )
|
248
|
-
|
170
|
+
# Add branch indexes to 'comment-line' and 'comment-block' state.
|
171
|
+
# From 'idle' we can branch to 'comment-line' or 'comment-block' states.
|
172
|
+
ps_idle.branches << parser.state_index( ps_cline )
|
173
|
+
ps_idle.branches << parser.state_index( ps_cblock )
|
174
|
+
|
175
|
+
# Describe 'comment-block' state.
|
176
|
+
# Set template for entry this state (String or Regexp).
|
177
|
+
ps_cblock.entry << '/'
|
178
|
+
ps_cblock.entry << '*'
|
179
|
+
ps_cblock.entry << '*'
|
180
|
+
# Set template for leave this state (String or Regexp).
|
181
|
+
ps_cblock.leave << '*'
|
182
|
+
ps_cblock.leave << '/'
|
183
|
+
# Set template for ignore this state (String format only).
|
184
|
+
ps_cblock.ignore << '*'
|
185
|
+
# Add handler to 'commaent-block' state.
|
249
186
|
ps_cblock.init( method(:doc_init) )
|
250
187
|
ps_cblock.handler( method(:doc_handler) )
|
251
188
|
ps_cblock.fini( method(:doc_fini) )
|
252
189
|
|
253
|
-
#
|
254
|
-
# Add all states to parser-machine.
|
255
|
-
#
|
256
|
-
parser.addstate ps_idle
|
257
|
-
parser.addstate ps_cline
|
258
|
-
parser.addstate ps_cblock
|
259
|
-
|
260
|
-
#
|
261
190
|
# Call parser startup method.
|
262
|
-
#
|
263
191
|
parser.prestart
|
264
192
|
|
265
|
-
#
|
266
|
-
# Call interactive mode for check state-machine.
|
267
|
-
#
|
268
193
|
$fout.puts "<html>"
|
269
194
|
$fout.puts "<body>"
|
270
195
|
|
@@ -281,7 +206,7 @@ $fout.close
|
|
281
206
|
|
282
207
|
Now developing of the simple parser has been finished. You can create test file, for example 'test.c':
|
283
208
|
|
284
|
-
*source №
|
209
|
+
*source № 3*:
|
285
210
|
```
|
286
211
|
#include <stdlib.h>
|
287
212
|
|
@@ -297,9 +222,9 @@ void test2 ( void )
|
|
297
222
|
}
|
298
223
|
```
|
299
224
|
|
300
|
-
and
|
225
|
+
and execute folow command in command line as:
|
301
226
|
|
302
|
-
$ ruby
|
227
|
+
$ ruby parser_example.rb test.c
|
303
228
|
|
304
229
|
After work, we should see a file named 'index.html'.
|
305
230
|
|
@@ -323,29 +248,46 @@ After work, we should see a file named 'index.html'.
|
|
323
248
|
|
324
249
|
**NOTE**: для ввода символа `'\'` необходимо набрать `'\\'`.
|
325
250
|
|
326
|
-
Если все переходы работают как мы и ожидали, то
|
327
|
-
можно перейти к написанию обработчиков наших состояний. Для этого допишем в наш скрипт код из *source № 2*
|
328
|
-
в начало файла и вместо строки `parser.interactive_parser` добавим код из *source № 3* и *source № 4*.
|
329
|
-
В результате должен получится код как на *source № 5*.
|
251
|
+
Если все переходы работают как мы и ожидали, то можно перейти к написанию обработчиков наших состояний.
|
330
252
|
|
331
253
|
Метод `doc_init` будет вызываться при входе в состояние, т.е. является конструктором состояния.
|
332
254
|
Метод `doc_handler` будет вызываться каждый раз, до тех пор пока парсер находится в состоянии `comment-line` или `comment-block`.
|
333
255
|
Метод `doc_fini` будет вызываться при выходе из состояния, т.е. является деструктором состояния.
|
334
256
|
|
335
|
-
|
336
|
-
|
337
|
-
обработки и метод
|
257
|
+
Обработчики состояния должены возвращать следующие значения: __nil__ - говорит парсер-машине о том,
|
258
|
+
что процесс разбора продолжается и метод `.parse` вернет `true` и любое отличное от __nil__ значение
|
259
|
+
будет разценено парсером как ошибка обработки и метод `.parse` вернет `false`. Для того, чтобы
|
260
|
+
прервать процесс процесс парсинга, следует анализировать возвращаемое значение метода `,parse`,
|
261
|
+
например так:
|
262
|
+
```ruby
|
263
|
+
parser = Iparser::Machine.new
|
264
|
+
'123asd'.each_char do |c|
|
265
|
+
exit if !parser.parse(c)
|
266
|
+
end
|
267
|
+
```
|
268
|
+
Также, каждое состояние имеет поле `branches`, для добавление индекса состояний на которые
|
269
|
+
возможен переход из текущего состояния.
|
338
270
|
|
339
271
|
Дополнительно для состояния `comment-block` мы указали символы, которые надо игнорировать,
|
340
272
|
а именно `'*'` и `doc_handler` не будет вызываться при наличия данного символа во входном потоке.
|
273
|
+
По окончанию мы получим файл с содержимым из *source № 2*.
|
341
274
|
|
342
|
-
И наконец создадим тестовый файл с именем 'test.c' и наполним его содержимым из *source №
|
275
|
+
И наконец создадим тестовый файл с именем 'test.c' и наполним его содержимым из *source № 3*.
|
343
276
|
Наш простой парсер готов. Теперь запустим его набрав следующую команду:
|
344
277
|
|
345
278
|
$ ruby parser_example.rb test.c
|
346
279
|
|
347
280
|
По окончанию работы мы должны увидеть файл с именем 'index.html'.
|
348
281
|
|
282
|
+
## Patch
|
283
|
+
|
284
|
+
Details information for each patch.
|
285
|
+
|
286
|
+
##### 1.1.4
|
287
|
+
* Add method `.state_index` for return index of parser-state.
|
288
|
+
* Add analyes result of any handler (init, fini, handler).
|
289
|
+
* Now `entry` and `leave` templates you can set a string or regular expression.
|
290
|
+
|
349
291
|
## Development
|
350
292
|
|
351
293
|
After checking out the repo, run `bin/setup` to install dependencies. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
data/iparser.gemspec
CHANGED
@@ -9,8 +9,8 @@ Gem::Specification.new do |spec|
|
|
9
9
|
spec.authors = ["gera-gas"]
|
10
10
|
spec.email = ["gera_box@mail.ru"]
|
11
11
|
|
12
|
-
spec.summary = %q{Universal parser machine
|
13
|
-
spec.description = %q{
|
12
|
+
spec.summary = %q{Universal parser machine implementation with interactive mode. Can be used as a parser engine.}
|
13
|
+
spec.description = %q{}
|
14
14
|
spec.homepage = "https://github.com/gera-gas/iparser"
|
15
15
|
spec.license = "MIT"
|
16
16
|
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
data/lib/iparser.rb
CHANGED
@@ -1,30 +1,22 @@
|
|
1
1
|
#--
|
2
2
|
# iparser.rb - Universal parser machine to generate your specific parsers.
|
3
3
|
#++
|
4
|
-
|
5
|
-
# Parser::Machine.new - Create parser machine.
|
6
|
-
#
|
7
|
-
require "iparser/version"
|
4
|
+
require 'iparser/version'
|
8
5
|
|
9
|
-
module Iparser
|
10
|
-
##
|
6
|
+
module Iparser
|
11
7
|
# Used for describe single state
|
12
8
|
# of parser machine.
|
13
|
-
#
|
14
9
|
class State
|
15
10
|
|
16
11
|
attr_reader :statename
|
17
12
|
attr_accessor :branches, :entry, :leave, :ientry, :ileave, :ignore
|
18
|
-
|
13
|
+
|
19
14
|
# call-seq:
|
20
15
|
# State.new( String )
|
21
|
-
#
|
22
|
-
# Class constructor.
|
23
|
-
#
|
24
16
|
def initialize ( sname )
|
25
|
-
|
26
|
-
|
27
|
-
|
17
|
+
unless sname.instance_of? String
|
18
|
+
raise TypeError, 'Incorrectly types for <Parser-State> constructor.'
|
19
|
+
end
|
28
20
|
|
29
21
|
@statename = sname
|
30
22
|
@init = nil # method called after entry (state constructor).
|
@@ -38,30 +30,22 @@ module Iparser
|
|
38
30
|
@branches = [] # indexes of any states to branch.
|
39
31
|
end
|
40
32
|
|
41
|
-
#
|
42
33
|
# call-seq:
|
43
34
|
# init( method(:some_init_method) )
|
44
35
|
#
|
45
36
|
# Set initializer method for current state.
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
m.instance_of? Method
|
50
|
-
|
51
|
-
@init = m
|
37
|
+
def init ( method )
|
38
|
+
raise TypeError, error_message(method, __method__) unless method.instance_of? Method
|
39
|
+
@init = method
|
52
40
|
end
|
53
41
|
|
54
|
-
#
|
55
42
|
# call-seq:
|
56
43
|
# fini( method(:some_fini_method) )
|
57
44
|
#
|
58
45
|
# Set finalizer method for current state.
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
m.instance_of? Method
|
63
|
-
|
64
|
-
@fini = m
|
46
|
+
def fini ( method )
|
47
|
+
raise TypeError, error_message(method, __method__) unless method.instance_of? Method
|
48
|
+
@fini = method
|
65
49
|
end
|
66
50
|
|
67
51
|
def run_init( *args ) # :nodoc:
|
@@ -74,39 +58,35 @@ module Iparser
|
|
74
58
|
return nil
|
75
59
|
end
|
76
60
|
|
77
|
-
#
|
78
61
|
# call-seq:
|
79
62
|
# handler( method(:some_handler_method) )
|
80
63
|
#
|
81
64
|
# Set handler method for current state.
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
h.instance_of? Method
|
86
|
-
|
87
|
-
@handler = h
|
65
|
+
def handler ( method )
|
66
|
+
raise TypeError, error_message(method, __method__) unless method.instance_of? Method
|
67
|
+
@handler = method
|
88
68
|
end
|
89
69
|
|
90
70
|
def run_handler( *args ) # :nodoc:
|
91
71
|
return @handler.call( *args ) if @handler != nil
|
92
72
|
return nil
|
93
73
|
end
|
94
|
-
|
74
|
+
|
75
|
+
private
|
76
|
+
def error_message(object, method)
|
77
|
+
"#{object.class}: Incorrectly types for <#{method}> method of <Parser-State>."
|
78
|
+
end
|
95
79
|
end # class State
|
80
|
+
|
96
81
|
|
97
|
-
|
98
|
-
##
|
99
82
|
# Used for create parser machine.
|
100
|
-
#
|
101
83
|
class Machine
|
84
|
+
INITIAL_STATE = 'wait'
|
102
85
|
|
103
86
|
attr_reader :parserstate
|
104
|
-
|
87
|
+
|
105
88
|
# call-seq:
|
106
89
|
# Machine.new( )
|
107
|
-
#
|
108
|
-
# Class constructor.
|
109
|
-
#
|
110
90
|
def initialize ( )
|
111
91
|
@buffer = [] # буфер для символов входного потока
|
112
92
|
@states = [] # массив с состояниями парсера, <ParserState> объекты
|
@@ -121,9 +101,7 @@ module Iparser
|
|
121
101
|
@parserstate = ''
|
122
102
|
end
|
123
103
|
|
124
|
-
#
|
125
104
|
# Сбрасывает чувствительные переменные.
|
126
|
-
#
|
127
105
|
def reset ( ) # :nodoc:
|
128
106
|
@buffer = []
|
129
107
|
@states.each do |s|
|
@@ -132,26 +110,21 @@ module Iparser
|
|
132
110
|
end
|
133
111
|
end
|
134
112
|
|
135
|
-
#
|
136
113
|
# Initialize parser object,
|
137
114
|
# should be called before call other methods.
|
138
|
-
#
|
139
115
|
def prestart ( )
|
140
116
|
reset( )
|
141
117
|
@matchstate[:state] = 0
|
142
118
|
@chain = [ @states[0], ]
|
143
119
|
end
|
144
120
|
|
145
|
-
#
|
146
121
|
# Display information about of each state of parser.
|
147
|
-
#
|
148
122
|
def display ( )
|
149
123
|
puts 'Parser states: ' + @states.size.to_s
|
150
124
|
|
151
125
|
@states.each do |st|
|
152
126
|
puts
|
153
|
-
puts '
|
154
|
-
puts 'state: ' + st.statename
|
127
|
+
puts '** state: ' + st.statename
|
155
128
|
puts 'branches: '
|
156
129
|
st.branches.each do |br|
|
157
130
|
puts ' ' + @states[br].statename
|
@@ -159,17 +132,15 @@ module Iparser
|
|
159
132
|
end
|
160
133
|
end
|
161
134
|
|
162
|
-
#
|
163
135
|
# Обработка ввода для интерактивных режимов работы.
|
164
|
-
#
|
165
136
|
def interactive_input ( ) # :nodoc:
|
166
137
|
state = 0
|
167
138
|
rv = ""
|
168
139
|
str = gets
|
169
|
-
|
140
|
+
|
170
141
|
# Сразу нажата <Enter> => exit.
|
171
142
|
return rv if str[0] == '\n'
|
172
|
-
|
143
|
+
|
173
144
|
# Выполняем разбор посимвольно.
|
174
145
|
str.each_char do |c|
|
175
146
|
break if c == ?\n
|
@@ -206,9 +177,7 @@ module Iparser
|
|
206
177
|
when 'f'
|
207
178
|
rv += "\f"
|
208
179
|
else
|
209
|
-
puts
|
210
|
-
puts 'ERROR: unrecognized esc-symbols.'
|
211
|
-
puts
|
180
|
+
puts "\nERROR: unrecognized esc-symbols.\n"
|
212
181
|
exit
|
213
182
|
end
|
214
183
|
state = 0
|
@@ -217,9 +186,7 @@ module Iparser
|
|
217
186
|
return rv
|
218
187
|
end
|
219
188
|
|
220
|
-
#
|
221
189
|
# Обработка вывода для интерактивных режимов работы.
|
222
|
-
#
|
223
190
|
def interactive_output( istr ) # :nodoc:
|
224
191
|
str = []
|
225
192
|
istr.bytes.each do |c|
|
@@ -247,82 +214,88 @@ module Iparser
|
|
247
214
|
return str
|
248
215
|
end
|
249
216
|
|
250
|
-
#
|
251
217
|
# Run parser machine for check in interactive mode.
|
252
|
-
#
|
253
218
|
def interactive_parser ( )
|
254
219
|
puts 'Press <Enter> to exit...'
|
255
|
-
|
220
|
+
|
256
221
|
# Цикл обработки ввода.
|
257
|
-
loop
|
222
|
+
loop do
|
258
223
|
str = interactive_input( )
|
259
224
|
break if str == ""
|
260
|
-
|
225
|
+
|
261
226
|
# Цикл посимвольной классификаци.
|
262
227
|
str.bytes.each do |c|
|
263
228
|
parse( c.chr )
|
264
229
|
puts 'parser: ' + @parserstate
|
265
230
|
puts 'symbol: ' + interactive_output( c.chr ).to_s
|
266
|
-
puts 'buffer: ' + @buffer.to_s
|
267
231
|
puts 'state: ' + @chain.last.statename
|
268
232
|
puts
|
269
233
|
end
|
270
|
-
|
234
|
+
end
|
271
235
|
end
|
272
236
|
|
273
|
-
#
|
274
237
|
# call-seq:
|
275
238
|
# s = Parser::State.new('idle')
|
276
239
|
# p = Parser::Machine.new
|
277
240
|
# p << s
|
278
241
|
#
|
279
242
|
# Add any parser-state to current parser.
|
280
|
-
#
|
281
243
|
def addstate ( ps )
|
282
244
|
raise TypeError, ps.class.to_s + ': Incorrectly types for \'<<\' method of <Parser>.' unless
|
283
245
|
ps.instance_of? State
|
284
|
-
|
285
246
|
@states << ps
|
286
247
|
end
|
248
|
+
|
249
|
+
# call-seq:
|
250
|
+
# some_state1.branches << parser.state_index(some_state2).
|
251
|
+
# Return index
|
252
|
+
def state_index ( state )
|
253
|
+
raise TypeError, ps.class.to_s + ': Incorrectly types for \'state_index\' method of <Parser>.' unless
|
254
|
+
state.instance_of? State
|
255
|
+
|
256
|
+
@states.each_with_index do |st,i|
|
257
|
+
return i if state == st
|
258
|
+
end
|
259
|
+
raise "State <#{state.statename}> is not exist in Parser."
|
260
|
+
end
|
287
261
|
|
288
|
-
#
|
289
262
|
# Сравнивает символы входного потока
|
290
263
|
# с символами из указанного шаблона.
|
291
264
|
# В качестве шаблона выступают поля <entry> или <leave>
|
292
265
|
# объектов типа <ParserState>.
|
293
|
-
#
|
294
266
|
def cmp ( tmp, idx ) # :nodoc:
|
295
|
-
|
267
|
+
|
296
268
|
# проверка на случай если шаблон не задан,
|
297
269
|
# т.е. проинициализирован в [].
|
298
|
-
#
|
299
270
|
if tmp.size > 0 then
|
300
271
|
if idx < tmp.size then
|
301
|
-
|
272
|
+
case tmp[idx].class.to_s
|
273
|
+
when 'Regexp'
|
274
|
+
return true if @buffer.last =~ tmp[ idx ]
|
275
|
+
when 'String'
|
276
|
+
return true if @buffer.last == tmp[ idx ]
|
277
|
+
end
|
302
278
|
end
|
303
279
|
end
|
304
280
|
return false
|
305
281
|
end
|
306
282
|
|
307
|
-
#
|
308
283
|
# Поиск в массиве указанного диапазона,
|
309
284
|
# указанного в параметрах символа.
|
310
285
|
#
|
311
286
|
# >=0 : индекс совпавшего элемента.
|
312
287
|
# -1 : нет совпадений.
|
313
|
-
#
|
314
288
|
def checkback ( tmp, len ) # :nodoc:
|
315
289
|
if len > 0 then
|
316
290
|
i = len
|
317
|
-
len.times
|
291
|
+
len.times do
|
318
292
|
i = i - 1
|
319
293
|
return i if cmp( tmp, i )
|
320
|
-
|
294
|
+
end
|
321
295
|
end
|
322
296
|
return -1
|
323
297
|
end
|
324
298
|
|
325
|
-
#
|
326
299
|
# Находит соответствие между символами входного потока
|
327
300
|
# и возможными переходами.
|
328
301
|
#
|
@@ -333,20 +306,17 @@ module Iparser
|
|
333
306
|
# -1 : возврат в предыдущее состояние
|
334
307
|
# -2 : еще идет проверка.
|
335
308
|
# -3 : нет cовпадений (промах).
|
336
|
-
#
|
337
309
|
def classify ( state ) # :nodoc:
|
338
310
|
case @matchstate[:state]
|
339
|
-
|
311
|
+
|
340
312
|
# Состояние еще не определено.
|
341
313
|
# :state = 0
|
342
|
-
#
|
343
314
|
when 0
|
344
315
|
mcount = 0
|
345
316
|
mindex = 0
|
346
317
|
backtag = 0
|
347
318
|
#
|
348
319
|
# Проверка условия выхода из состояния.
|
349
|
-
#
|
350
320
|
if cmp( state.leave, state.ileave ) then
|
351
321
|
state.ileave = state.ileave.next
|
352
322
|
#
|
@@ -375,7 +345,6 @@ module Iparser
|
|
375
345
|
#
|
376
346
|
# Проверка возможных переходов для
|
377
347
|
# указанного в параметрах состояния.
|
378
|
-
#
|
379
348
|
state.branches.each do |b|
|
380
349
|
if cmp( @states[b].entry, @states[b].ientry ) then
|
381
350
|
mcount = mcount + 1
|
@@ -383,7 +352,6 @@ module Iparser
|
|
383
352
|
@states[b].ientry = @states[b].ientry.next
|
384
353
|
#
|
385
354
|
# состояние полностью пройдено.
|
386
|
-
#
|
387
355
|
if @states[ b ].ientry >= @states[ b ].entry.size then
|
388
356
|
return b
|
389
357
|
end
|
@@ -393,7 +361,6 @@ module Iparser
|
|
393
361
|
# успешна, то возможно входной символ совпадает
|
394
362
|
# с предыдущими, уже совпавшими символами,
|
395
363
|
# т.е. как откат в режиме <wait>.
|
396
|
-
#
|
397
364
|
i = checkback( @states[b].entry, @states[b].ientry )
|
398
365
|
|
399
366
|
if i != -1 then
|
@@ -407,7 +374,6 @@ module Iparser
|
|
407
374
|
end
|
408
375
|
#
|
409
376
|
# Анализ количества совпадений.
|
410
|
-
#
|
411
377
|
case mcount
|
412
378
|
#
|
413
379
|
# нет совпадений.
|
@@ -416,7 +382,6 @@ module Iparser
|
|
416
382
|
#
|
417
383
|
# однозначное совпадение, но весь массив шаблонов
|
418
384
|
# еще не пройден.
|
419
|
-
#
|
420
385
|
when 1
|
421
386
|
@matchstate[:state] = 1
|
422
387
|
@matchstate[:index] = mindex
|
@@ -426,10 +391,9 @@ module Iparser
|
|
426
391
|
else
|
427
392
|
return -2
|
428
393
|
end
|
429
|
-
|
394
|
+
|
430
395
|
# Состояние точно определено.
|
431
396
|
# :state = 1
|
432
|
-
#
|
433
397
|
when 1
|
434
398
|
i = @matchstate[:index]
|
435
399
|
if cmp( @states[ i ].entry, @states[ i ].ientry ) then
|
@@ -439,7 +403,6 @@ module Iparser
|
|
439
403
|
#
|
440
404
|
# Массив шаблонов совпадает полностью.
|
441
405
|
# можно считать, что 100% совпадение.
|
442
|
-
#
|
443
406
|
if @states[ i ].ientry >= @states[ i ].entry.size then
|
444
407
|
@matchstate[:state] = 0
|
445
408
|
return i
|
@@ -451,7 +414,6 @@ module Iparser
|
|
451
414
|
# успешна, то возможно входной символ совпадает
|
452
415
|
# с предыдущими, уже совпавшими символами,
|
453
416
|
# т.е. как откат в режиме <wait>.
|
454
|
-
#
|
455
417
|
idx = checkback( @states[i].entry, @states[i].ientry )
|
456
418
|
|
457
419
|
if idx != -1 then
|
@@ -460,20 +422,16 @@ module Iparser
|
|
460
422
|
end
|
461
423
|
@states[i].ientry = 0
|
462
424
|
@matchstate[:state] = 0
|
463
|
-
#return (-3 + backtag)
|
464
|
-
#return -4
|
465
425
|
return -3
|
466
426
|
end # case @matchstate
|
467
427
|
end
|
468
428
|
|
469
|
-
#
|
470
429
|
# Main method, used for parse input stream.
|
471
430
|
# Parse will be starting in unit with nil index (0).
|
472
431
|
#
|
473
432
|
# Return true if parsing process is successful, else return false.
|
474
|
-
#
|
475
433
|
def parse ( c )
|
476
|
-
@parserstate =
|
434
|
+
@parserstate = INITIAL_STATE
|
477
435
|
retval = true
|
478
436
|
#
|
479
437
|
# * Фиксированное состояние (определенное): -1.
|
@@ -481,46 +439,46 @@ module Iparser
|
|
481
439
|
# * Переход (смена состояний): >0.
|
482
440
|
#
|
483
441
|
@buffer << c
|
484
|
-
|
485
|
-
#
|
442
|
+
|
486
443
|
# Проверка переходов в другие состояния.
|
487
|
-
#
|
488
444
|
r = classify( @chain.last )
|
489
445
|
|
490
|
-
#
|
491
446
|
# Переход (прыжок) в другое состояние.
|
492
447
|
# <branch>:
|
493
|
-
#
|
494
448
|
if r >= 0 then
|
495
449
|
@chain << @states[r]
|
496
|
-
@chain.last.run_init( @buffer )
|
497
|
-
|
498
|
-
|
499
|
-
|
450
|
+
if @chain.last.run_init( @buffer ) == nil then
|
451
|
+
reset( )
|
452
|
+
@parserstate = 'branch'
|
453
|
+
else
|
454
|
+
@parserstate = 'error'
|
455
|
+
retval = false
|
456
|
+
end
|
457
|
+
|
500
458
|
# Возврат из текущего состояния.
|
501
459
|
# <back>:
|
502
|
-
#
|
503
460
|
elsif r == -1 then
|
504
|
-
@chain.last.run_fini( @buffer )
|
505
|
-
|
506
|
-
|
507
|
-
|
508
|
-
|
509
|
-
|
510
|
-
|
461
|
+
if @chain.last.run_fini( @buffer ) == nil then
|
462
|
+
#
|
463
|
+
# если это состояние не первое в цепочке
|
464
|
+
# тогда откатываемся назад.
|
465
|
+
if @chain.size > 1 then
|
466
|
+
@chain.delete_at( @chain.size - 1 )
|
467
|
+
end
|
468
|
+
reset( )
|
469
|
+
@parserstate = 'back'
|
470
|
+
else
|
471
|
+
@parserstate = 'error'
|
472
|
+
retval = false
|
511
473
|
end
|
512
|
-
|
513
|
-
@parserstate = 'back'
|
514
|
-
#
|
474
|
+
|
515
475
|
# Нет совпадений.
|
516
476
|
# <miss>:
|
517
|
-
#
|
518
477
|
elsif r == -3 then
|
519
478
|
#
|
520
479
|
# если в процессе состояния <wait>
|
521
480
|
# мы попали в <miss>, то накопленный
|
522
481
|
# буфер надо обработать.
|
523
|
-
#
|
524
482
|
@buffer.each do |ch|
|
525
483
|
@parserstate = 'miss'
|
526
484
|
tag = true
|
@@ -531,7 +489,6 @@ module Iparser
|
|
531
489
|
r = @chain.last.run_handler( ch )
|
532
490
|
#
|
533
491
|
# Анализ результата обработки состояния.
|
534
|
-
#
|
535
492
|
case r.class.to_s
|
536
493
|
#
|
537
494
|
# Fixnum - переход на любое состояние (индекс).
|
@@ -549,7 +506,6 @@ module Iparser
|
|
549
506
|
#
|
550
507
|
# else - расценивается как ошибка обработки.
|
551
508
|
# обработка ложится на плечи разработчика.
|
552
|
-
#
|
553
509
|
else
|
554
510
|
@parserstate = 'error'
|
555
511
|
retval = false
|
@@ -563,7 +519,6 @@ module Iparser
|
|
563
519
|
end
|
564
520
|
|
565
521
|
private :reset, :cmp, :checkback, :interactive_input, :interactive_output
|
566
|
-
|
567
522
|
end # class Machine
|
568
523
|
|
569
|
-
end
|
524
|
+
end
|
data/lib/iparser/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: iparser
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.1.
|
4
|
+
version: 1.1.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- gera-gas
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-
|
11
|
+
date: 2015-12-07 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -38,7 +38,7 @@ dependencies:
|
|
38
38
|
- - "~>"
|
39
39
|
- !ruby/object:Gem::Version
|
40
40
|
version: '10.0'
|
41
|
-
description:
|
41
|
+
description: ''
|
42
42
|
email:
|
43
43
|
- gera_box@mail.ru
|
44
44
|
executables: []
|
@@ -79,6 +79,7 @@ rubyforge_project:
|
|
79
79
|
rubygems_version: 2.2.2
|
80
80
|
signing_key:
|
81
81
|
specification_version: 4
|
82
|
-
summary: Universal parser machine
|
82
|
+
summary: Universal parser machine implementation with interactive mode. Can be used
|
83
|
+
as a parser engine.
|
83
84
|
test_files: []
|
84
85
|
has_rdoc:
|