iparser 1.1.1
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 +7 -0
- data/.gitignore +9 -0
- data/CODE_OF_CONDUCT.md +13 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +21 -0
- data/README.md +227 -0
- data/Rakefile +1 -0
- data/bin/console +14 -0
- data/bin/setup +7 -0
- data/iparser.gemspec +23 -0
- data/lib/iparser/version.rb +3 -0
- data/lib/iparser.rb +569 -0
- metadata +84 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 74b3def90b2fb587ba964156f44d311a931f9978
|
4
|
+
data.tar.gz: b6fa824918d59bcce6ec0867ef778c4b097b3475
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 8a56f7c33f4582f05e655ea0d1106d4e6f54ead2d57c274d6fb018ff942160ae2594df1b346b16ff5307ae0bb48adee902ee1b501f6e6589dd9c8f002f11a26b
|
7
|
+
data.tar.gz: d3426a1e514372f664b8801caab325ebd66c3063c4c7732efe868daf3f5abec4d1285264dbaf9122a943d9d8ec016f2fdddb97178265e5d18ac0fc6808d0a309
|
data/.gitignore
ADDED
data/CODE_OF_CONDUCT.md
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
# Contributor Code of Conduct
|
2
|
+
|
3
|
+
As contributors and maintainers of this project, we pledge to respect all people who contribute through reporting issues, posting feature requests, updating documentation, submitting pull requests or patches, and other activities.
|
4
|
+
|
5
|
+
We are committed to making participation in this project a harassment-free experience for everyone, regardless of level of experience, gender, gender identity and expression, sexual orientation, disability, personal appearance, body size, race, ethnicity, age, or religion.
|
6
|
+
|
7
|
+
Examples of unacceptable behavior by participants include the use of sexual language or imagery, derogatory comments or personal attacks, trolling, public or private harassment, insults, or other unprofessional conduct.
|
8
|
+
|
9
|
+
Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct. Project maintainers who do not follow the Code of Conduct may be removed from the project team.
|
10
|
+
|
11
|
+
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by opening an issue or contacting one or more of the project maintainers.
|
12
|
+
|
13
|
+
This Code of Conduct is adapted from the [Contributor Covenant](http://contributor-covenant.org), version 1.0.0, available at [http://contributor-covenant.org/version/1/0/0/](http://contributor-covenant.org/version/1/0/0/)
|
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2015 anton
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,227 @@
|
|
1
|
+
# Iparser
|
2
|
+
|
3
|
+
Universal parser machine to generate your specific parsers.
|
4
|
+
Used for simple and fast create your specific parsers.
|
5
|
+
|
6
|
+
## Installation
|
7
|
+
|
8
|
+
Add this line to your application's Gemfile:
|
9
|
+
|
10
|
+
```ruby
|
11
|
+
gem 'parser'
|
12
|
+
```
|
13
|
+
|
14
|
+
And then execute:
|
15
|
+
|
16
|
+
$ bundle
|
17
|
+
|
18
|
+
Or install it yourself as:
|
19
|
+
|
20
|
+
$ gem install parser
|
21
|
+
|
22
|
+
## Usage
|
23
|
+
|
24
|
+
For example usage, present here very simple parser for automatically generate documentation from source code.
|
25
|
+
|
26
|
+
*source № 1*:
|
27
|
+
```ruby
|
28
|
+
#
|
29
|
+
# Create parser-machine object.
|
30
|
+
#
|
31
|
+
parser = Iparser::Machine.new
|
32
|
+
|
33
|
+
#
|
34
|
+
# Create startup state for this parser-machine.
|
35
|
+
#
|
36
|
+
ps_idle = Iparser::State.new('idle')
|
37
|
+
|
38
|
+
#
|
39
|
+
# Add branch indexes to 'comment-line' and 'comment-block' state.
|
40
|
+
#
|
41
|
+
ps_idle.branches << 1
|
42
|
+
ps_idle.branches << 2
|
43
|
+
|
44
|
+
#
|
45
|
+
# Create single line comment state for this parser-machine.
|
46
|
+
#
|
47
|
+
ps_cline = Iparser::State.new('comment-line')
|
48
|
+
ps_cline.entry << /\//
|
49
|
+
ps_cline.entry << /\//
|
50
|
+
ps_cline.entry << /\//
|
51
|
+
ps_cline.leave << /[\n\r]/
|
52
|
+
|
53
|
+
#
|
54
|
+
# Create multiline comment state for this parser-machine.
|
55
|
+
#
|
56
|
+
ps_cblock = Iparser::State.new('comment-block')
|
57
|
+
ps_cblock.entry << /\//
|
58
|
+
ps_cblock.entry << /\*/
|
59
|
+
ps_cblock.entry << /\*/
|
60
|
+
ps_cblock.leave << /\*/
|
61
|
+
ps_cblock.leave << /\//
|
62
|
+
ps_cblock.ignore << '*'
|
63
|
+
|
64
|
+
#
|
65
|
+
# Add all states to parser-machine.
|
66
|
+
#
|
67
|
+
parser.addstate ps_idle
|
68
|
+
parser.addstate ps_cline
|
69
|
+
parser.addstate ps_cblock
|
70
|
+
|
71
|
+
#
|
72
|
+
# Call parser startup method.
|
73
|
+
#
|
74
|
+
parser.prestart
|
75
|
+
|
76
|
+
#
|
77
|
+
# Call interactive mode for check state-machine.
|
78
|
+
#
|
79
|
+
parser.interactive_parser
|
80
|
+
```
|
81
|
+
|
82
|
+
Run this script and typing `'///'` for branch to 'comment-line' state. Then type `'\n'` or `'\r'` for leave this state.
|
83
|
+
**NOTE**: Type `'\\'` for input `'\'`. Check each state.
|
84
|
+
After successfully check, add the following code to the beginning of the file:
|
85
|
+
|
86
|
+
*source № 2*:
|
87
|
+
```ruby
|
88
|
+
#
|
89
|
+
# Simple check startup arguments.
|
90
|
+
#
|
91
|
+
if( ARGV.size != 1 || !File.exist?(ARGV[0]) )
|
92
|
+
puts
|
93
|
+
puts "ERROR: unable to open file #{ARGV[0]}"
|
94
|
+
puts
|
95
|
+
exit
|
96
|
+
end
|
97
|
+
#
|
98
|
+
# Create output file.
|
99
|
+
#
|
100
|
+
$fout = File.new( 'index.html', 'w' )
|
101
|
+
|
102
|
+
#
|
103
|
+
# Create initializer method for parser-states.
|
104
|
+
#
|
105
|
+
def doc_init ( str )
|
106
|
+
$fout.print "<p>"
|
107
|
+
end
|
108
|
+
#
|
109
|
+
# Create handler method for parser-states.
|
110
|
+
#
|
111
|
+
def doc_handler ( c )
|
112
|
+
$fout.print c
|
113
|
+
end
|
114
|
+
#
|
115
|
+
# Create finalizer method for parser-states.
|
116
|
+
#
|
117
|
+
def doc_fini ( str )
|
118
|
+
$fout.puts "</p>"
|
119
|
+
end
|
120
|
+
```
|
121
|
+
Method `doc_init` is state contructor.
|
122
|
+
Method `doc_handler` is state handler and call in `comment-line` or `comment-block` for each input char.
|
123
|
+
Method `doc_fini` is state destructor.
|
124
|
+
|
125
|
+
Handler may be return following data types: `Fixnum` - index to branch (>=0),
|
126
|
+
`NilClass` - hold current state (nil) and any data types for break parse (error, method `parse`
|
127
|
+
return `false`).
|
128
|
+
|
129
|
+
For `comment-block` state set ignore char - '*', and handler don't called to this chars.
|
130
|
+
|
131
|
+
Add the following code instead `parser.interactive_parser`:
|
132
|
+
|
133
|
+
*source № 3*:
|
134
|
+
```ruby
|
135
|
+
$fout.puts "<html>"
|
136
|
+
$fout.puts "<body>"
|
137
|
+
|
138
|
+
File.open( ARGV[0], 'r' ).each do |line|
|
139
|
+
line.each_char do |c|
|
140
|
+
parser.parse(c)
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
$fout.puts "</body>"
|
145
|
+
$fout.puts "</html>"
|
146
|
+
$fout.close
|
147
|
+
```
|
148
|
+
Now developing of the simple parser has been finished. You can create test file, for example 'test.c':
|
149
|
+
|
150
|
+
*source № 4*:
|
151
|
+
```
|
152
|
+
#include <stdlib.h>
|
153
|
+
|
154
|
+
///Test function - 1.
|
155
|
+
void test1 ( void )
|
156
|
+
{
|
157
|
+
}
|
158
|
+
/**
|
159
|
+
* Test function - 2.
|
160
|
+
*/
|
161
|
+
void test2 ( void )
|
162
|
+
{
|
163
|
+
}
|
164
|
+
```
|
165
|
+
|
166
|
+
and do folow command in command line as:
|
167
|
+
|
168
|
+
$ ruby <you parser script name>.rb test.c
|
169
|
+
|
170
|
+
After work, we should see a file named 'index.html'.
|
171
|
+
|
172
|
+
###### Своих не бросаем!
|
173
|
+
------------------------
|
174
|
+
|
175
|
+
Для примера использования данного gem, напишем простой парсер файла исходного кода
|
176
|
+
для автоматической генерации документации, отдаленно напоминающего doxygen.
|
177
|
+
Создадим ruby скрипт c именем 'parser_example.rb' и наполним его содержимым из *source № 1*.
|
178
|
+
|
179
|
+
Далее запустим скрипт на выполнение:
|
180
|
+
|
181
|
+
$ ruby parser_example.rb
|
182
|
+
|
183
|
+
Теперь в интерактивном режиме проверим корректность описанных нами состояний: `comment-line` и `comment-block`.
|
184
|
+
Для чего введем строку символов `///` и увидим что наш парсер вывел `branch to <comment-line>`, это говорит
|
185
|
+
о том, что парсер перешол в состояние для обработки однострочных комментариев, т.е. в состояние `comment-line`.
|
186
|
+
Теперь введем `\n` или `\r` и увидим что парсер покинул состояние `comment-line` и вернулся в состояние `idle`.
|
187
|
+
Аналогично проведем проверку для всех оставшихся состояний.
|
188
|
+
|
189
|
+
**NOTE**: для ввода символа `'\'` необходимо набрать `'\\'`.
|
190
|
+
|
191
|
+
Если все переходы работают как мы и ожидали, то
|
192
|
+
можно перейти к написанию обработчиков наших состояний. Для этого допишем в наш скрипт код из *source № 2*
|
193
|
+
в начало файла и вместо строки `parser.interactive_parser` добавим код из *source № 3*.
|
194
|
+
|
195
|
+
Метод `doc_init` будет вызываться при входе в состояние, т.е. является конструктором состояния.
|
196
|
+
Метод `doc_handler` будет вызываться каждый раз, до тех пор пока парсер находится в состоянии `comment-line` или `comment-block`.
|
197
|
+
Метод `doc_fini` будет вызываться при выходе из состояния, т.е. является деструктором состояния.
|
198
|
+
|
199
|
+
Обработчик состояния должен возвращать следующие типы данных: `Fixnum` - индекс состояния на которое надо перейти (>=0),
|
200
|
+
`NilClass` - оставаться в текущем состоянии (nil) и остальные типы данных которые будут расценены как ошибка
|
201
|
+
обработки и метод `parse` вернет `false` (error). Во всех остальных случаях `parse` возвращает `true`.
|
202
|
+
|
203
|
+
Дополнительно для состояния `comment-block` мы указали символы, которые надо игнорировать,
|
204
|
+
а именно `'*'` и `doc_handler` не будет вызываться при наличия данного символа во входном потоке.
|
205
|
+
|
206
|
+
И наконец создадим тестовый файл с именем 'test.c' и наполним его содержимым из *source № 4*.
|
207
|
+
Наш простой парсер готов. Теперь запустим его набрав следующую команду:
|
208
|
+
|
209
|
+
$ ruby parser_example.rb test.c
|
210
|
+
|
211
|
+
По окончанию работы мы должны увидеть файл с именем 'index.html'.
|
212
|
+
|
213
|
+
## Development
|
214
|
+
|
215
|
+
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.
|
216
|
+
|
217
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
218
|
+
|
219
|
+
## Contributing
|
220
|
+
|
221
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/parser. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](contributor-covenant.org) code of conduct.
|
222
|
+
|
223
|
+
|
224
|
+
## License
|
225
|
+
|
226
|
+
The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
|
227
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "iparser"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require "irb"
|
14
|
+
IRB.start
|
data/bin/setup
ADDED
data/iparser.gemspec
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'iparser/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "iparser"
|
8
|
+
spec.version = Iparser::VERSION
|
9
|
+
spec.authors = ["gera-gas"]
|
10
|
+
spec.email = ["gera_box@mail.ru"]
|
11
|
+
|
12
|
+
spec.summary = %q{Universal parser machine to generate your specific parsers.}
|
13
|
+
spec.description = %q{Universal parser machine implementation with interactive mode.}
|
14
|
+
spec.homepage = "https://github.com/gera-gas/iparser"
|
15
|
+
spec.license = "MIT"
|
16
|
+
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
17
|
+
spec.bindir = "exe"
|
18
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_development_dependency "bundler", "~> 1.10"
|
22
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
23
|
+
end
|
data/lib/iparser.rb
ADDED
@@ -0,0 +1,569 @@
|
|
1
|
+
#--
|
2
|
+
# iparser.rb - Universal parser machine to generate your specific parsers.
|
3
|
+
#++
|
4
|
+
# Parser::State.new - Create single state for parser.
|
5
|
+
# Parser::Machine.new - Create parser machine.
|
6
|
+
#
|
7
|
+
require "iparser/version"
|
8
|
+
|
9
|
+
module Iparser
|
10
|
+
##
|
11
|
+
# Used for describe single state
|
12
|
+
# of parser machine.
|
13
|
+
#
|
14
|
+
class State
|
15
|
+
|
16
|
+
attr_reader :statename
|
17
|
+
attr_accessor :branches, :entry, :leave, :ientry, :ileave, :ignore
|
18
|
+
#
|
19
|
+
# call-seq:
|
20
|
+
# State.new( String )
|
21
|
+
#
|
22
|
+
# Class constructor.
|
23
|
+
#
|
24
|
+
def initialize ( sname )
|
25
|
+
|
26
|
+
raise TypeError, 'Incorrectly types for <ParserState> constructor.' unless
|
27
|
+
sname.instance_of? String
|
28
|
+
|
29
|
+
@statename = sname
|
30
|
+
@init = nil # method called after entry (state constructor).
|
31
|
+
@fini = nil # method called after leave (state destructor).
|
32
|
+
@handler = nil # state machine for handler current state.
|
33
|
+
@ignore = []
|
34
|
+
@ientry = 0 # use to save compred index of this state.
|
35
|
+
@ileave = 0 # use to save compred index of this state.
|
36
|
+
@entry = [] # template chars for entry state.
|
37
|
+
@leave = [] # template chars for leave state.
|
38
|
+
@branches = [] # indexes of any states to branch.
|
39
|
+
end
|
40
|
+
|
41
|
+
#
|
42
|
+
# call-seq:
|
43
|
+
# init( method(:some_init_method) )
|
44
|
+
#
|
45
|
+
# Set initializer method for current state.
|
46
|
+
#
|
47
|
+
def init ( m )
|
48
|
+
raise TypeError, m.class.to_s + ': Incorrectly types for <init> method of <ParserState>.' unless
|
49
|
+
m.instance_of? Method
|
50
|
+
|
51
|
+
@init = m
|
52
|
+
end
|
53
|
+
|
54
|
+
#
|
55
|
+
# call-seq:
|
56
|
+
# fini( method(:some_fini_method) )
|
57
|
+
#
|
58
|
+
# Set finalizer method for current state.
|
59
|
+
#
|
60
|
+
def fini ( m )
|
61
|
+
raise TypeError, m.class.to_s + ': Incorrectly types for <fini> method of <ParserState>.' unless
|
62
|
+
m.instance_of? Method
|
63
|
+
|
64
|
+
@fini = m
|
65
|
+
end
|
66
|
+
|
67
|
+
def run_init( *args ) # :nodoc:
|
68
|
+
return @init.call( *args ) if @init != nil
|
69
|
+
return nil
|
70
|
+
end
|
71
|
+
|
72
|
+
def run_fini( *args ) # :nodoc:
|
73
|
+
return @fini.call( *args ) if @fini != nil
|
74
|
+
return nil
|
75
|
+
end
|
76
|
+
|
77
|
+
#
|
78
|
+
# call-seq:
|
79
|
+
# handler( method(:some_handler_method) )
|
80
|
+
#
|
81
|
+
# Set handler method for current state.
|
82
|
+
#
|
83
|
+
def handler ( h )
|
84
|
+
raise TypeError, h.class.to_s + ': Incorrectly types for <handler> method of <ParserState>.' unless
|
85
|
+
h.instance_of? Method
|
86
|
+
|
87
|
+
@handler = h
|
88
|
+
end
|
89
|
+
|
90
|
+
def run_handler( *args ) # :nodoc:
|
91
|
+
return @handler.call( *args ) if @handler != nil
|
92
|
+
return nil
|
93
|
+
end
|
94
|
+
|
95
|
+
end # class State
|
96
|
+
|
97
|
+
|
98
|
+
##
|
99
|
+
# Used for create parser machine.
|
100
|
+
#
|
101
|
+
class Machine
|
102
|
+
|
103
|
+
attr_reader :parserstate
|
104
|
+
#
|
105
|
+
# call-seq:
|
106
|
+
# Machine.new( )
|
107
|
+
#
|
108
|
+
# Class constructor.
|
109
|
+
#
|
110
|
+
def initialize ( )
|
111
|
+
@buffer = [] # буфер для символов входного потока
|
112
|
+
@states = [] # массив с состояниями парсера, <ParserState> объекты
|
113
|
+
@chain = [] # цепочка работающих состояний
|
114
|
+
#
|
115
|
+
# Машина состояний для метода classify.
|
116
|
+
#
|
117
|
+
@matchstate = {
|
118
|
+
:state => 0,
|
119
|
+
:index => 0
|
120
|
+
}
|
121
|
+
@parserstate = ''
|
122
|
+
end
|
123
|
+
|
124
|
+
#
|
125
|
+
# Сбрасывает чувствительные переменные.
|
126
|
+
#
|
127
|
+
def reset ( ) # :nodoc:
|
128
|
+
@buffer = []
|
129
|
+
@states.each do |s|
|
130
|
+
s.ientry = 0
|
131
|
+
s.ileave = 0
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
#
|
136
|
+
# Initialize parser object,
|
137
|
+
# should be called before call other methods.
|
138
|
+
#
|
139
|
+
def prestart ( )
|
140
|
+
reset( )
|
141
|
+
@matchstate[:state] = 0
|
142
|
+
@chain = [ @states[0], ]
|
143
|
+
end
|
144
|
+
|
145
|
+
#
|
146
|
+
# Display information about of each state of parser.
|
147
|
+
#
|
148
|
+
def display ( )
|
149
|
+
puts 'Parser states: ' + @states.size.to_s
|
150
|
+
|
151
|
+
@states.each do |st|
|
152
|
+
puts
|
153
|
+
puts '*' * 80
|
154
|
+
puts 'state: ' + st.statename
|
155
|
+
puts 'branches: '
|
156
|
+
st.branches.each do |br|
|
157
|
+
puts ' ' + @states[br].statename
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
#
|
163
|
+
# Обработка ввода для интерактивных режимов работы.
|
164
|
+
#
|
165
|
+
def interactive_input ( ) # :nodoc:
|
166
|
+
state = 0
|
167
|
+
rv = ""
|
168
|
+
str = gets
|
169
|
+
#
|
170
|
+
# Сразу нажата <Enter> => exit.
|
171
|
+
return rv if str[0] == '\n'
|
172
|
+
#
|
173
|
+
# Выполняем разбор посимвольно.
|
174
|
+
str.each_char do |c|
|
175
|
+
break if c == ?\n
|
176
|
+
case state
|
177
|
+
#
|
178
|
+
# Сборка символов и проверка на наличие
|
179
|
+
# экранирующего символа, значит это ESC-символы.
|
180
|
+
when 0
|
181
|
+
if c == '\\' then
|
182
|
+
state = 1
|
183
|
+
else
|
184
|
+
rv += c
|
185
|
+
end
|
186
|
+
#
|
187
|
+
# Анализ ESC символа.
|
188
|
+
when 1
|
189
|
+
case c
|
190
|
+
when '0'
|
191
|
+
rv += "\0"
|
192
|
+
when 'n'
|
193
|
+
rv += "\n"
|
194
|
+
when 'r'
|
195
|
+
rv += "\r"
|
196
|
+
when '\\'
|
197
|
+
rv += "\\"
|
198
|
+
when 'a'
|
199
|
+
rv += "\a"
|
200
|
+
when 'b'
|
201
|
+
rv += "\b"
|
202
|
+
when 't'
|
203
|
+
rv += "\t"
|
204
|
+
when 'v'
|
205
|
+
rv += "\v"
|
206
|
+
when 'f'
|
207
|
+
rv += "\f"
|
208
|
+
else
|
209
|
+
puts
|
210
|
+
puts 'ERROR: unrecognized esc-symbols.'
|
211
|
+
puts
|
212
|
+
exit
|
213
|
+
end
|
214
|
+
state = 0
|
215
|
+
end
|
216
|
+
end
|
217
|
+
return rv
|
218
|
+
end
|
219
|
+
|
220
|
+
#
|
221
|
+
# Обработка вывода для интерактивных режимов работы.
|
222
|
+
#
|
223
|
+
def interactive_output( istr ) # :nodoc:
|
224
|
+
str = []
|
225
|
+
istr.bytes.each do |c|
|
226
|
+
case c
|
227
|
+
when 0
|
228
|
+
str << c.to_s + ":\\0"
|
229
|
+
when 10
|
230
|
+
str << c.to_s + ":\\n"
|
231
|
+
when 13
|
232
|
+
str << c.to_s + ":\\r"
|
233
|
+
when 7
|
234
|
+
str << c.to_s + ":\\a"
|
235
|
+
when 8
|
236
|
+
str << c.to_s + ":\\b"
|
237
|
+
when 9
|
238
|
+
str << c.to_s + ":\\t"
|
239
|
+
when 11
|
240
|
+
str << c.to_s + ":\\v"
|
241
|
+
when 12
|
242
|
+
str << c.to_s + ":\\f"
|
243
|
+
else
|
244
|
+
str << c.to_s + ":" + c.chr
|
245
|
+
end
|
246
|
+
end
|
247
|
+
return str
|
248
|
+
end
|
249
|
+
|
250
|
+
#
|
251
|
+
# Run parser machine for check in interactive mode.
|
252
|
+
#
|
253
|
+
def interactive_parser ( )
|
254
|
+
puts 'Press <Enter> to exit...'
|
255
|
+
#
|
256
|
+
# Цикл обработки ввода.
|
257
|
+
loop {
|
258
|
+
str = interactive_input( )
|
259
|
+
break if str == ""
|
260
|
+
#
|
261
|
+
# Цикл посимвольной классификаци.
|
262
|
+
str.bytes.each do |c|
|
263
|
+
parse( c.chr )
|
264
|
+
puts 'parser: ' + @parserstate
|
265
|
+
puts 'symbol: ' + interactive_output( c.chr ).to_s
|
266
|
+
puts 'buffer: ' + @buffer.to_s
|
267
|
+
puts 'state: ' + @chain.last.statename
|
268
|
+
puts
|
269
|
+
end
|
270
|
+
}
|
271
|
+
end
|
272
|
+
|
273
|
+
#
|
274
|
+
# call-seq:
|
275
|
+
# s = Parser::State.new('idle')
|
276
|
+
# p = Parser::Machine.new
|
277
|
+
# p << s
|
278
|
+
#
|
279
|
+
# Add any parser-state to current parser.
|
280
|
+
#
|
281
|
+
def addstate ( ps )
|
282
|
+
raise TypeError, ps.class.to_s + ': Incorrectly types for \'<<\' method of <Parser>.' unless
|
283
|
+
ps.instance_of? State
|
284
|
+
|
285
|
+
@states << ps
|
286
|
+
end
|
287
|
+
|
288
|
+
#
|
289
|
+
# Сравнивает символы входного потока
|
290
|
+
# с символами из указанного шаблона.
|
291
|
+
# В качестве шаблона выступают поля <entry> или <leave>
|
292
|
+
# объектов типа <ParserState>.
|
293
|
+
#
|
294
|
+
def cmp ( tmp, idx ) # :nodoc:
|
295
|
+
#
|
296
|
+
# проверка на случай если шаблон не задан,
|
297
|
+
# т.е. проинициализирован в [].
|
298
|
+
#
|
299
|
+
if tmp.size > 0 then
|
300
|
+
if idx < tmp.size then
|
301
|
+
return true if @buffer.last =~ tmp[ idx ]
|
302
|
+
end
|
303
|
+
end
|
304
|
+
return false
|
305
|
+
end
|
306
|
+
|
307
|
+
#
|
308
|
+
# Поиск в массиве указанного диапазона,
|
309
|
+
# указанного в параметрах символа.
|
310
|
+
#
|
311
|
+
# >=0 : индекс совпавшего элемента.
|
312
|
+
# -1 : нет совпадений.
|
313
|
+
#
|
314
|
+
def checkback ( tmp, len ) # :nodoc:
|
315
|
+
if len > 0 then
|
316
|
+
i = len
|
317
|
+
len.times {
|
318
|
+
i = i - 1
|
319
|
+
return i if cmp( tmp, i )
|
320
|
+
}
|
321
|
+
end
|
322
|
+
return -1
|
323
|
+
end
|
324
|
+
|
325
|
+
#
|
326
|
+
# Находит соответствие между символами входного потока
|
327
|
+
# и возможными переходами.
|
328
|
+
#
|
329
|
+
# При совпадени возвращает индекс состояния
|
330
|
+
# в массиве состояний, иначе:
|
331
|
+
#
|
332
|
+
# >0 : прыжок в новое состояние
|
333
|
+
# -1 : возврат в предыдущее состояние
|
334
|
+
# -2 : еще идет проверка.
|
335
|
+
# -3 : нет cовпадений (промах).
|
336
|
+
#
|
337
|
+
def classify ( state ) # :nodoc:
|
338
|
+
case @matchstate[:state]
|
339
|
+
#
|
340
|
+
# Состояние еще не определено.
|
341
|
+
# :state = 0
|
342
|
+
#
|
343
|
+
when 0
|
344
|
+
mcount = 0
|
345
|
+
mindex = 0
|
346
|
+
backtag = 0
|
347
|
+
#
|
348
|
+
# Проверка условия выхода из состояния.
|
349
|
+
#
|
350
|
+
if cmp( state.leave, state.ileave ) then
|
351
|
+
state.ileave = state.ileave.next
|
352
|
+
#
|
353
|
+
# Возврат в предыдущее состояние.
|
354
|
+
if state.ileave >= state.leave.size then
|
355
|
+
return -1
|
356
|
+
end
|
357
|
+
backtag = 1
|
358
|
+
else
|
359
|
+
#
|
360
|
+
# Нет совпадения, но если уже часть сравнений
|
361
|
+
# успешна, то возможно входной символ совпадает
|
362
|
+
# с предыдущими, уже совпавшими символами,
|
363
|
+
# т.е. как откат в режиме <wait>.
|
364
|
+
#
|
365
|
+
i = checkback( state.leave, state.ileave )
|
366
|
+
|
367
|
+
if i != -1 then
|
368
|
+
state.ileave = i.next
|
369
|
+
backtag = 1
|
370
|
+
else
|
371
|
+
state.ileave = 0
|
372
|
+
backtag = 0
|
373
|
+
end
|
374
|
+
end
|
375
|
+
#
|
376
|
+
# Проверка возможных переходов для
|
377
|
+
# указанного в параметрах состояния.
|
378
|
+
#
|
379
|
+
state.branches.each do |b|
|
380
|
+
if cmp( @states[b].entry, @states[b].ientry ) then
|
381
|
+
mcount = mcount + 1
|
382
|
+
mindex = b
|
383
|
+
@states[b].ientry = @states[b].ientry.next
|
384
|
+
#
|
385
|
+
# состояние полностью пройдено.
|
386
|
+
#
|
387
|
+
if @states[ b ].ientry >= @states[ b ].entry.size then
|
388
|
+
return b
|
389
|
+
end
|
390
|
+
else
|
391
|
+
#
|
392
|
+
# Нет совпадения, но если уже часть сравнений
|
393
|
+
# успешна, то возможно входной символ совпадает
|
394
|
+
# с предыдущими, уже совпавшими символами,
|
395
|
+
# т.е. как откат в режиме <wait>.
|
396
|
+
#
|
397
|
+
i = checkback( @states[b].entry, @states[b].ientry )
|
398
|
+
|
399
|
+
if i != -1 then
|
400
|
+
mcount = mcount + 1
|
401
|
+
mindex = b
|
402
|
+
@states[b].ientry = i.next
|
403
|
+
else
|
404
|
+
@states[b].ientry = 0
|
405
|
+
end
|
406
|
+
end
|
407
|
+
end
|
408
|
+
#
|
409
|
+
# Анализ количества совпадений.
|
410
|
+
#
|
411
|
+
case mcount
|
412
|
+
#
|
413
|
+
# нет совпадений.
|
414
|
+
when 0
|
415
|
+
return (-3 + backtag)
|
416
|
+
#
|
417
|
+
# однозначное совпадение, но весь массив шаблонов
|
418
|
+
# еще не пройден.
|
419
|
+
#
|
420
|
+
when 1
|
421
|
+
@matchstate[:state] = 1
|
422
|
+
@matchstate[:index] = mindex
|
423
|
+
return -2
|
424
|
+
#
|
425
|
+
# нет однозначного соответствия.
|
426
|
+
else
|
427
|
+
return -2
|
428
|
+
end
|
429
|
+
##
|
430
|
+
# Состояние точно определено.
|
431
|
+
# :state = 1
|
432
|
+
#
|
433
|
+
when 1
|
434
|
+
i = @matchstate[:index]
|
435
|
+
if cmp( @states[ i ].entry, @states[ i ].ientry ) then
|
436
|
+
#
|
437
|
+
# Инкремент счетчика (индекса) вхождений.
|
438
|
+
@states[ i ].ientry = @states[ i ].ientry.next
|
439
|
+
#
|
440
|
+
# Массив шаблонов совпадает полностью.
|
441
|
+
# можно считать, что 100% совпадение.
|
442
|
+
#
|
443
|
+
if @states[ i ].ientry >= @states[ i ].entry.size then
|
444
|
+
@matchstate[:state] = 0
|
445
|
+
return i
|
446
|
+
end
|
447
|
+
return -2
|
448
|
+
end
|
449
|
+
#
|
450
|
+
# Нет совпадения, но если уже часть сравнений
|
451
|
+
# успешна, то возможно входной символ совпадает
|
452
|
+
# с предыдущими, уже совпавшими символами,
|
453
|
+
# т.е. как откат в режиме <wait>.
|
454
|
+
#
|
455
|
+
idx = checkback( @states[i].entry, @states[i].ientry )
|
456
|
+
|
457
|
+
if idx != -1 then
|
458
|
+
@states[i].ientry = idx.next
|
459
|
+
return -2
|
460
|
+
end
|
461
|
+
@states[i].ientry = 0
|
462
|
+
@matchstate[:state] = 0
|
463
|
+
#return (-3 + backtag)
|
464
|
+
#return -4
|
465
|
+
return -3
|
466
|
+
end # case @matchstate
|
467
|
+
end
|
468
|
+
|
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
|
+
#
|
475
|
+
def parse ( c )
|
476
|
+
@parserstate = 'wait'
|
477
|
+
retval = true
|
478
|
+
#
|
479
|
+
# * Фиксированное состояние (определенное): -1.
|
480
|
+
# * Не фиксированное состояние (неопределенное): -2.
|
481
|
+
# * Переход (смена состояний): >0.
|
482
|
+
#
|
483
|
+
@buffer << c
|
484
|
+
|
485
|
+
#
|
486
|
+
# Проверка переходов в другие состояния.
|
487
|
+
#
|
488
|
+
r = classify( @chain.last )
|
489
|
+
|
490
|
+
#
|
491
|
+
# Переход (прыжок) в другое состояние.
|
492
|
+
# <branch>:
|
493
|
+
#
|
494
|
+
if r >= 0 then
|
495
|
+
@chain << @states[r]
|
496
|
+
@chain.last.run_init( @buffer )
|
497
|
+
reset( )
|
498
|
+
@parserstate = 'branch'
|
499
|
+
#
|
500
|
+
# Возврат из текущего состояния.
|
501
|
+
# <back>:
|
502
|
+
#
|
503
|
+
elsif r == -1 then
|
504
|
+
@chain.last.run_fini( @buffer )
|
505
|
+
#
|
506
|
+
# если это состояние не первое в цепочке
|
507
|
+
# тогда откатываемся назад.
|
508
|
+
#
|
509
|
+
if @chain.size > 1 then
|
510
|
+
@chain.delete_at( @chain.size - 1 )
|
511
|
+
end
|
512
|
+
reset( )
|
513
|
+
@parserstate = 'back'
|
514
|
+
#
|
515
|
+
# Нет совпадений.
|
516
|
+
# <miss>:
|
517
|
+
#
|
518
|
+
elsif r == -3 then
|
519
|
+
#
|
520
|
+
# если в процессе состояния <wait>
|
521
|
+
# мы попали в <miss>, то накопленный
|
522
|
+
# буфер надо обработать.
|
523
|
+
#
|
524
|
+
@buffer.each do |ch|
|
525
|
+
@parserstate = 'miss'
|
526
|
+
tag = true
|
527
|
+
if @chain.last.ignore.size > 0 then
|
528
|
+
tag = false if @chain.last.ignore.include?(ch)
|
529
|
+
end
|
530
|
+
if tag == true then
|
531
|
+
r = @chain.last.run_handler( ch )
|
532
|
+
#
|
533
|
+
# Анализ результата обработки состояния.
|
534
|
+
#
|
535
|
+
case r.class.to_s
|
536
|
+
#
|
537
|
+
# Fixnum - переход на любое состояние (индекс).
|
538
|
+
when 'Fixnum'
|
539
|
+
if( (r >= 0) && (r < @states.size) ) then
|
540
|
+
@chain << @states[r]
|
541
|
+
reset( )
|
542
|
+
@parserstate = 'hardset'
|
543
|
+
else
|
544
|
+
raise TypeError, "Method <#{@chain.last.statename}> return incorrectly index."
|
545
|
+
end
|
546
|
+
#
|
547
|
+
# nil - ничего не возвращает.
|
548
|
+
when 'NilClass'
|
549
|
+
#
|
550
|
+
# else - расценивается как ошибка обработки.
|
551
|
+
# обработка ложится на плечи разработчика.
|
552
|
+
#
|
553
|
+
else
|
554
|
+
@parserstate = 'error'
|
555
|
+
retval = false
|
556
|
+
break
|
557
|
+
end
|
558
|
+
end
|
559
|
+
end
|
560
|
+
@buffer = []
|
561
|
+
end
|
562
|
+
return retval
|
563
|
+
end
|
564
|
+
|
565
|
+
private :reset, :cmp, :checkback, :interactive_input, :interactive_output
|
566
|
+
|
567
|
+
end # class Machine
|
568
|
+
|
569
|
+
end # Module Iparser
|
metadata
ADDED
@@ -0,0 +1,84 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: iparser
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.1.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- gera-gas
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-11-29 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.10'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.10'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '10.0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '10.0'
|
41
|
+
description: Universal parser machine implementation with interactive mode.
|
42
|
+
email:
|
43
|
+
- gera_box@mail.ru
|
44
|
+
executables: []
|
45
|
+
extensions: []
|
46
|
+
extra_rdoc_files: []
|
47
|
+
files:
|
48
|
+
- ".gitignore"
|
49
|
+
- CODE_OF_CONDUCT.md
|
50
|
+
- Gemfile
|
51
|
+
- LICENSE.txt
|
52
|
+
- README.md
|
53
|
+
- Rakefile
|
54
|
+
- bin/console
|
55
|
+
- bin/setup
|
56
|
+
- iparser.gemspec
|
57
|
+
- lib/iparser.rb
|
58
|
+
- lib/iparser/version.rb
|
59
|
+
homepage: https://github.com/gera-gas/iparser
|
60
|
+
licenses:
|
61
|
+
- MIT
|
62
|
+
metadata: {}
|
63
|
+
post_install_message:
|
64
|
+
rdoc_options: []
|
65
|
+
require_paths:
|
66
|
+
- lib
|
67
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
68
|
+
requirements:
|
69
|
+
- - ">="
|
70
|
+
- !ruby/object:Gem::Version
|
71
|
+
version: '0'
|
72
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
73
|
+
requirements:
|
74
|
+
- - ">="
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
version: '0'
|
77
|
+
requirements: []
|
78
|
+
rubyforge_project:
|
79
|
+
rubygems_version: 2.2.2
|
80
|
+
signing_key:
|
81
|
+
specification_version: 4
|
82
|
+
summary: Universal parser machine to generate your specific parsers.
|
83
|
+
test_files: []
|
84
|
+
has_rdoc:
|