rhetor 0.2.0
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 +15 -0
- data/.rspec +3 -0
- data/.rubocop.yml +5 -0
- data/.travis.yml +4 -0
- data/CHANGELOG.md +19 -0
- data/Gemfile +7 -0
- data/LICENSE.txt +22 -0
- data/README.md +102 -0
- data/Rakefile +12 -0
- data/lib/rhetor.rb +6 -0
- data/lib/rhetor/exceptions.rb +9 -0
- data/lib/rhetor/lexical_analyser.rb +158 -0
- data/lib/rhetor/token.rb +18 -0
- data/lib/rhetor/version.rb +3 -0
- data/rhetor.gemspec +32 -0
- data/spec/lexical_analyser_spec.rb +91 -0
- data/spec/spec_helper.rb +7 -0
- data/spec/support/coverage.rb +11 -0
- data/spec/token_spec.rb +13 -0
- metadata +182 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 3f2702dc837acf720a37f663d219b2c3b59a13dc
|
4
|
+
data.tar.gz: 5fa1338de3e9f990594c8fa411e5eb24cfe07569
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: a93d30c92bbb94a8805bf01d310077512535388d5df106b7bf678c5e220687668db307a442a19e2678acd68f7a4ceedcf826fd1aaf3bb86ff872f21bd6ca1e5a
|
7
|
+
data.tar.gz: a6d0a3b06821db3a832098e9759d1b6366b9eb4253ed70992da035ab9ec9a0443865ed3926debe342aad061cb81504721942e5a05bd8ca7d7062a98be1ce7df0
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.rubocop.yml
ADDED
data/.travis.yml
ADDED
data/CHANGELOG.md
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
# Change log
|
2
|
+
|
3
|
+
## master (unreleased)
|
4
|
+
|
5
|
+
## 0.1.0 (2015-01-06)
|
6
|
+
|
7
|
+
### New features
|
8
|
+
|
9
|
+
* You can provide a block for LexicalAnalyser#analyse method and it will be run with each of encountered tokens.
|
10
|
+
* Tokens have values instead of corresponding substrings. You can provide an evaluator for any rule. It is a block that will be called if the pattern is encountered. It should receive a matched substring and return the value of the corresponding token. If the evaluator is omitted, the value of the token will coincide with the matched substring.
|
11
|
+
* Tokens can be represented as a string via to_s method
|
12
|
+
|
13
|
+
### Changes
|
14
|
+
|
15
|
+
* Update example in README.md
|
16
|
+
|
17
|
+
### Bugs fixed
|
18
|
+
|
19
|
+
* Fix Rake task `test:coverage'
|
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2015 Egor Dubenetskiy
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,102 @@
|
|
1
|
+
# Rhetor
|
2
|
+
|
3
|
+
[](https://github.com/edubenetskiy/rhetor)
|
4
|
+
[](https://www.rubygems.org/gems/rhetor)
|
5
|
+
[](http://www.rubydoc.info/gems/rhetor/frames)
|
6
|
+
[](#license)
|
7
|
+
|
8
|
+
[](https://gemnasium.com/edubenetskiy/rhetor)
|
9
|
+
[](https://coveralls.io/r/edubenetskiy/rhetor?branch=master)
|
10
|
+
[](https://travis-ci.org/edubenetskiy/rhetor)
|
11
|
+
[](https://codeclimate.com/github/edubenetskiy/rhetor)
|
12
|
+
[](https://github.com/edubenetskiy/rhetor/releases)
|
13
|
+
|
14
|
+
|
15
|
+
Rhetor is yet another lexical analyser library written in pure Ruby
|
16
|
+
with no runtime dependencies. It is properly documented and easy-to-use.
|
17
|
+
|
18
|
+
## Installation
|
19
|
+
|
20
|
+
Add this line to your application's Gemfile:
|
21
|
+
|
22
|
+
```ruby
|
23
|
+
gem 'rhetor'
|
24
|
+
```
|
25
|
+
|
26
|
+
And then execute:
|
27
|
+
|
28
|
+
$ bundle
|
29
|
+
|
30
|
+
Or install it yourself as:
|
31
|
+
|
32
|
+
$ gem install rhetor
|
33
|
+
|
34
|
+
## Usage
|
35
|
+
|
36
|
+
```ruby
|
37
|
+
# Define behaviour
|
38
|
+
lexer = Rhetor::LexicalAnalyser.new do
|
39
|
+
# Within this block you can define your own rules, your own patterns
|
40
|
+
# to recognize. You can call these methods out of this block
|
41
|
+
# like this: lexer.rule(...), it also works.
|
42
|
+
rule '+', :plus
|
43
|
+
rule '-', :minus
|
44
|
+
rule '*', :asterisk
|
45
|
+
rule '/', :solidus
|
46
|
+
rule '(', :left_parenthesis
|
47
|
+
rule ')', :right_parenthesis
|
48
|
+
|
49
|
+
# This rule will return tokens with floating-point values.
|
50
|
+
# Actually, you can make Rhetor to return tokens with any types of values,
|
51
|
+
# you only have to provide an evaluator - a block which receives a matched
|
52
|
+
# substring and returns the desired value. Like here:
|
53
|
+
rule(/[-+]?[0-9]*\.?[0-9]+/, :number) { |string| string.to_f }
|
54
|
+
|
55
|
+
# Here we make Rhetor to ignore whitespaces via the regular expression:
|
56
|
+
ignore(/\s+/)
|
57
|
+
# We could also do it with a string: ignore ' ',
|
58
|
+
# but regular expressions are more powerful.
|
59
|
+
end
|
60
|
+
|
61
|
+
# Analyse method returns an array of tokens. You can provide a block
|
62
|
+
# and it will be run for each of encountered tokens.
|
63
|
+
tokens = lexer.analyse('2 + 2 * 2 - (25 / (3 + 70 / 4))') do |token|
|
64
|
+
puts token
|
65
|
+
end
|
66
|
+
# It produces:
|
67
|
+
# (number: 2.0 [0,1])
|
68
|
+
# (plus: "+" [2,1])
|
69
|
+
# (number: 2.0 [4,1])
|
70
|
+
# (asterisk: "*" [6,1])
|
71
|
+
# (number: 2.0 [8,1])
|
72
|
+
# (minus: "-" [10,1])
|
73
|
+
# (left_parenthesis: "(" [12,1])
|
74
|
+
# (number: 25.0 [13,2])
|
75
|
+
# (solidus: "/" [16,1])
|
76
|
+
# (left_parenthesis: "(" [18,1])
|
77
|
+
# (number: 3.0 [19,1])
|
78
|
+
# (plus: "+" [21,1])
|
79
|
+
# (number: 70.0 [23,2])
|
80
|
+
# (solidus: "/" [26,1])
|
81
|
+
# (number: 4.0 [28,1])
|
82
|
+
# (right_parenthesis: ")" [29,1])
|
83
|
+
# (right_parenthesis: ")" [30,1])
|
84
|
+
|
85
|
+
# Now get information about any of the tokens:
|
86
|
+
token = tokens.first # => (number: 2.0 [0,1])
|
87
|
+
token.value # => 2.0
|
88
|
+
token.name # => :number
|
89
|
+
token.position # => 0
|
90
|
+
token.length # => 1
|
91
|
+
```
|
92
|
+
|
93
|
+
## Contributing
|
94
|
+
|
95
|
+
1. Fork it (https://github.com/edubenetskiy/rhetor)
|
96
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
97
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
98
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
99
|
+
5. Create a new Pull Request
|
100
|
+
|
101
|
+
## License
|
102
|
+
Rhetor © 2015 by Egor Dubenetskiy. Rhetor is licensed under the MIT license. Please see LICENSE.txt for further details.
|
data/Rakefile
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
require 'bundler/gem_tasks'
|
2
|
+
require 'yard'
|
3
|
+
require 'rspec/core/rake_task'
|
4
|
+
require 'rubocop/rake_task'
|
5
|
+
require 'inch/rake'
|
6
|
+
|
7
|
+
Inch::Rake::Suggest.new
|
8
|
+
YARD::Rake::YardocTask.new
|
9
|
+
RuboCop::RakeTask.new
|
10
|
+
RSpec::Core::RakeTask.new('spec')
|
11
|
+
|
12
|
+
task default: %w(spec rubocop)
|
data/lib/rhetor.rb
ADDED
@@ -0,0 +1,9 @@
|
|
1
|
+
module Rhetor
|
2
|
+
RhetorError = Class.new(StandardError)
|
3
|
+
UnmatchedString = Class.new(RhetorError)
|
4
|
+
InvalidRuleName = Class.new(RhetorError)
|
5
|
+
RuleAlreadyExists = Class.new(RhetorError)
|
6
|
+
InvalidPattern = Class.new(RhetorError)
|
7
|
+
InvalidString = Class.new(RhetorError)
|
8
|
+
NoStringLoaded = Class.new(RhetorError)
|
9
|
+
end
|
@@ -0,0 +1,158 @@
|
|
1
|
+
require 'rhetor/exceptions'
|
2
|
+
require 'rhetor/token'
|
3
|
+
|
4
|
+
module Rhetor
|
5
|
+
# LexicalAnalyser is a class that performs lexical analysis
|
6
|
+
# of strings using a set of predefined rules.
|
7
|
+
#
|
8
|
+
# @!attribute [r] string
|
9
|
+
# @return [String] the string being analyzed
|
10
|
+
# @!attribute [r] position
|
11
|
+
# @return [Integer] the current position of analyser
|
12
|
+
#
|
13
|
+
# @author Egor Dubenetskiy <edubenetskiy@ya.ru>
|
14
|
+
#
|
15
|
+
class LexicalAnalyser
|
16
|
+
attr_reader :string, :position
|
17
|
+
|
18
|
+
# Creates a new lexical analyser and evaluates the passed block within it
|
19
|
+
# @param block [Block] the block to be executed
|
20
|
+
# @example Creating a simple HQ9+ parser
|
21
|
+
# lexer = Rhetor::LexicalAnalyser.new {
|
22
|
+
# rule 'H', :hello_world
|
23
|
+
# rule 'Q', :quine
|
24
|
+
# rule '9', :ninety_nine_bottles
|
25
|
+
# rule '+', :increment
|
26
|
+
# ignore /\s+/
|
27
|
+
# }
|
28
|
+
# @return [void]
|
29
|
+
#
|
30
|
+
def initialize(&block)
|
31
|
+
@string_patterns = {}
|
32
|
+
@regexp_patterns = {}
|
33
|
+
@ignored = []
|
34
|
+
@used_names = []
|
35
|
+
@evaluator = {}
|
36
|
+
@string = nil
|
37
|
+
@position = nil
|
38
|
+
(block.arity == 1) ? block[self] : instance_eval(&block) if block_given?
|
39
|
+
end
|
40
|
+
|
41
|
+
# Makes the analyser to recognize some pattern
|
42
|
+
# @param pattern [String, Regexp] the pattern
|
43
|
+
# @param name [Symbol] the name of the rule
|
44
|
+
# @param evaluator [Proc,nil] a proc. This proc will be called
|
45
|
+
# if the pattern is encountered. It receives a matched substring
|
46
|
+
# and calculates the value of the corresponding token. If this
|
47
|
+
# argument is omitted, the value of the token will coincide
|
48
|
+
# with the matched substring.
|
49
|
+
# @raise [InvalidPattern] if the pattern is not valid
|
50
|
+
# @raise [InvalidRuleName] unless the name of the rule is a symbol
|
51
|
+
# @raise [RuleAlreadyExists] if the rule with the same name already exists
|
52
|
+
# @return [void]
|
53
|
+
#
|
54
|
+
def rule(pattern, name, &evaluator)
|
55
|
+
check_rule(pattern, name)
|
56
|
+
@used_names.push name
|
57
|
+
@evaluator[name] = evaluator
|
58
|
+
array_name = "@#{pattern.class.name.downcase}_patterns".intern
|
59
|
+
instance_variable_get(array_name)[name] = pattern
|
60
|
+
end
|
61
|
+
|
62
|
+
# Makes the analyser to ignore some pattern
|
63
|
+
# @param pattern [String, Regexp] the pattern to be ignored
|
64
|
+
# @return [void]
|
65
|
+
#
|
66
|
+
def ignore(pattern)
|
67
|
+
fail InvalidPattern unless [String, Regexp].include? pattern.class
|
68
|
+
@ignored.push pattern unless @ignored.include? pattern
|
69
|
+
end
|
70
|
+
|
71
|
+
# Initiates the analysis of the string
|
72
|
+
# @param string [String] the string to be analyzed
|
73
|
+
# @return [void]
|
74
|
+
#
|
75
|
+
def begin_analysis(string)
|
76
|
+
fail InvalidString unless string.is_a? String
|
77
|
+
@string = string
|
78
|
+
@position = 0
|
79
|
+
@size = string.size
|
80
|
+
end
|
81
|
+
|
82
|
+
# @return [Token] the next token found in the string
|
83
|
+
# @raise [NoStringLoaded] if no string is being analyzed
|
84
|
+
# @raise [UnmatchedString] if the analyser is unable to get the next token
|
85
|
+
#
|
86
|
+
def next_token
|
87
|
+
fail NoStringLoaded unless @string
|
88
|
+
@position = skip_ignored(@string, @position)
|
89
|
+
return EOF_TOKEN if @position >= @size
|
90
|
+
name, length = string_pattern(@string, @position)
|
91
|
+
name, length = regexp_pattern(@string, @position) if length == 0
|
92
|
+
fail UnmatchedString, "at position #{@position}" if length == 0
|
93
|
+
token = make_token(name, @position, length)
|
94
|
+
@position += length
|
95
|
+
token
|
96
|
+
end
|
97
|
+
|
98
|
+
# Analyzes the given string
|
99
|
+
# @param string [String] the string to be analyzed
|
100
|
+
# @yieldparam token [Token] every encountered token
|
101
|
+
# @return [Array<Token>] the array of encountered tokens
|
102
|
+
#
|
103
|
+
def analyse(string, &block)
|
104
|
+
begin_analysis(string)
|
105
|
+
tokens = []
|
106
|
+
loop do
|
107
|
+
last_token = next_token
|
108
|
+
(last_token == EOF_TOKEN) ? break : tokens << last_token
|
109
|
+
block.call(last_token) if block_given?
|
110
|
+
end
|
111
|
+
tokens
|
112
|
+
end
|
113
|
+
|
114
|
+
private
|
115
|
+
|
116
|
+
def make_token(name, position, size)
|
117
|
+
substring = @string[position, size]
|
118
|
+
value = @evaluator[name] ? @evaluator[name].call(substring) : substring
|
119
|
+
Rhetor::Token.new(value, name, position, size)
|
120
|
+
end
|
121
|
+
|
122
|
+
def check_rule(pattern, name)
|
123
|
+
fail InvalidPattern unless [String, Regexp].include? pattern.class
|
124
|
+
fail InvalidRuleName unless name.is_a? Symbol
|
125
|
+
fail RuleAlreadyExists if @used_names.include? name
|
126
|
+
end
|
127
|
+
|
128
|
+
def string_pattern(string, position)
|
129
|
+
results = @string_patterns.map do |name, pattern|
|
130
|
+
[name, matched_size(pattern, string, position)]
|
131
|
+
end
|
132
|
+
results.max_by(&:last) || [nil, 0]
|
133
|
+
end
|
134
|
+
|
135
|
+
def regexp_pattern(string, position)
|
136
|
+
results = @regexp_patterns.map do |name, pattern|
|
137
|
+
[name, matched_size(pattern, string, position)]
|
138
|
+
end
|
139
|
+
# results.max_by(&:last) || [nil, 0]
|
140
|
+
results.sort_by(&:last).find { |_name, size| size > 0 } || [nil, 0]
|
141
|
+
end
|
142
|
+
|
143
|
+
def skip_ignored(string, position)
|
144
|
+
skipped = @ignored.map { |p| matched_size(p, string, position) }.max
|
145
|
+
skipped ? position + skipped : position
|
146
|
+
end
|
147
|
+
|
148
|
+
def matched_size(pattern, string, position)
|
149
|
+
if pattern.is_a? String
|
150
|
+
(string[position, pattern.size] == pattern) ? pattern.size : 0
|
151
|
+
elsif pattern.is_a? Regexp
|
152
|
+
md = string.match(pattern, position)
|
153
|
+
return 0 unless md
|
154
|
+
md.begin(0) == position ? md[0].size : 0
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
158
|
+
end
|
data/lib/rhetor/token.rb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
module Rhetor
|
2
|
+
# Token class represents tokens matched from the input.
|
3
|
+
#
|
4
|
+
# @attr_reader [Object] value the value of the token
|
5
|
+
# @attr_reader [Symbol] name the name of the matched rule
|
6
|
+
# @attr_reader [Integer] position the position of the matched substring
|
7
|
+
# @attr_reader [Integer] length the length of the matched substring
|
8
|
+
#
|
9
|
+
Token = Struct.new :value, :name, :position, :length do
|
10
|
+
def to_s
|
11
|
+
"(#{name}: #{value.inspect} [#{position},#{length}])"
|
12
|
+
end
|
13
|
+
|
14
|
+
alias_method :inspect, :to_s
|
15
|
+
end
|
16
|
+
|
17
|
+
EOF_TOKEN = Token.new(nil, nil, -1, nil)
|
18
|
+
end
|
data/rhetor.gemspec
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'rhetor/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = 'rhetor'
|
8
|
+
spec.version = Rhetor::VERSION
|
9
|
+
spec.authors = ['Egor Dubenetskiy']
|
10
|
+
spec.email = ['edubenetskiy@ya.ru']
|
11
|
+
spec.summary = 'Rhetor is yet another lexical analyser written in Ruby'
|
12
|
+
spec.description = 'Rhetor is yet another lexical analyser library
|
13
|
+
written in pure Ruby with no runtime dependencies,
|
14
|
+
properly documented and easy-to-use.'
|
15
|
+
spec.homepage = 'https://github.com/edubenetskiy/rhetor/'
|
16
|
+
spec.license = 'MIT'
|
17
|
+
spec.required_ruby_version = '>= 1.9.3'
|
18
|
+
|
19
|
+
spec.files = `git ls-files -z`.split("\x0")
|
20
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
21
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
22
|
+
spec.require_paths = ['lib']
|
23
|
+
|
24
|
+
spec.add_development_dependency 'bundler', '~> 1.7'
|
25
|
+
spec.add_development_dependency 'rake', '~> 10.4'
|
26
|
+
spec.add_development_dependency 'simplecov', '~> 0.9'
|
27
|
+
spec.add_development_dependency 'rspec', '~> 3.1'
|
28
|
+
spec.add_development_dependency 'rubocop', '~> 0.28'
|
29
|
+
spec.add_development_dependency 'rubocop-rspec', '~> 1.2'
|
30
|
+
spec.add_development_dependency 'inch', '~> 0.5'
|
31
|
+
spec.add_development_dependency 'yard', '~> 0.8'
|
32
|
+
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Rhetor do
|
4
|
+
describe LexicalAnalyser do
|
5
|
+
subject('analyser') do
|
6
|
+
described_class.new do |t|
|
7
|
+
t.rule 'key', :key
|
8
|
+
t.rule 'keyword', :keyword
|
9
|
+
t.rule 'word', :word
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
it 'returns an empty array being called with an empty string' do
|
14
|
+
expect(analyser.analyse '').to eq []
|
15
|
+
end
|
16
|
+
|
17
|
+
it 'returns an empty array if ignores anything' do
|
18
|
+
analyser.ignore(/.+/)
|
19
|
+
expect(analyser.analyse 'something anything').to eq []
|
20
|
+
end
|
21
|
+
|
22
|
+
it 'does not recognize an improper string' do
|
23
|
+
expect { analyser.analyse 'something' }.to raise_error(UnmatchedString)
|
24
|
+
end
|
25
|
+
|
26
|
+
it 'recognizes a single keyword' do
|
27
|
+
expect(analyser.analyse 'word').to eq [Token.new('word', :word, 0, 4)]
|
28
|
+
expect(analyser.analyse 'key').to eq [Token.new('key', :key, 0, 3)]
|
29
|
+
end
|
30
|
+
|
31
|
+
it 'recognizes a single pattern' do
|
32
|
+
analyser.rule(/\w+/, :custom_word)
|
33
|
+
expect(analyser.analyse 'something').to eq \
|
34
|
+
[Token.new('something', :custom_word, 0, 9)]
|
35
|
+
end
|
36
|
+
|
37
|
+
it 'recognizes a sequence of patterns and keywords' do
|
38
|
+
analyser.rule(/\w+/, :custom_word)
|
39
|
+
analyser.ignore(/\s+/)
|
40
|
+
expect(analyser.analyse 'some word').to eq \
|
41
|
+
[Token.new('some', :custom_word, 0, 4),
|
42
|
+
Token.new('word', :word, 5, 4)]
|
43
|
+
end
|
44
|
+
|
45
|
+
it 'takes only the longest token of several overlapped tokens' do
|
46
|
+
expect(analyser.analyse 'keyword').to eq \
|
47
|
+
[Token.new('keyword', :keyword, 0, 7)]
|
48
|
+
end
|
49
|
+
|
50
|
+
it 'recognizes several consecutive keywords' do
|
51
|
+
expect(analyser.analyse 'wordkeykeyword').to eq \
|
52
|
+
[Token.new('word', :word, 0, 4),
|
53
|
+
Token.new('key', :key, 4, 3),
|
54
|
+
Token.new('keyword', :keyword, 7, 7)]
|
55
|
+
end
|
56
|
+
|
57
|
+
it 'forbids the creation of rules with identical names' do
|
58
|
+
expect do
|
59
|
+
analyser.rule('key', :key)
|
60
|
+
end.to raise_error(RuleAlreadyExists)
|
61
|
+
end
|
62
|
+
|
63
|
+
it 'checks the validity of the names of the rules' do
|
64
|
+
expect do
|
65
|
+
analyser.rule('something', 'invalid rule name')
|
66
|
+
end.to raise_error(InvalidRuleName)
|
67
|
+
end
|
68
|
+
|
69
|
+
it 'checks the validity of the pattern' do
|
70
|
+
expect do
|
71
|
+
analyser.rule(:invalid_rule_pattern, :invalid_rule)
|
72
|
+
end.to raise_error(InvalidPattern)
|
73
|
+
end
|
74
|
+
|
75
|
+
it 'skips specified patterns' do
|
76
|
+
analyser.ignore(/\s+/)
|
77
|
+
expect(analyser.analyse 'word key keyword').to eq \
|
78
|
+
[Token.new('word', :word, 0, 4),
|
79
|
+
Token.new('key', :key, 5, 3),
|
80
|
+
Token.new('keyword', :keyword, 9, 7)]
|
81
|
+
end
|
82
|
+
|
83
|
+
it 'executes a passed block' do
|
84
|
+
expect do |b|
|
85
|
+
analyser.analyse('wordkeykeyword', &b)
|
86
|
+
end.to yield_successive_args(Token.new('word', :word, 0, 4),
|
87
|
+
Token.new('key', :key, 4, 3),
|
88
|
+
Token.new('keyword', :keyword, 7, 7))
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
data/spec/spec_helper.rb
ADDED
data/spec/token_spec.rb
ADDED
metadata
ADDED
@@ -0,0 +1,182 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: rhetor
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.2.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Egor Dubenetskiy
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2018-09-30 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.7'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.7'
|
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.4'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '10.4'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: simplecov
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0.9'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0.9'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rspec
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '3.1'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '3.1'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rubocop
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0.28'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0.28'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: rubocop-rspec
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - "~>"
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '1.2'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - "~>"
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '1.2'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: inch
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - "~>"
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '0.5'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - "~>"
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0.5'
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: yard
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - "~>"
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0.8'
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - "~>"
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '0.8'
|
125
|
+
description: |-
|
126
|
+
Rhetor is yet another lexical analyser library
|
127
|
+
written in pure Ruby with no runtime dependencies,
|
128
|
+
properly documented and easy-to-use.
|
129
|
+
email:
|
130
|
+
- edubenetskiy@ya.ru
|
131
|
+
executables: []
|
132
|
+
extensions: []
|
133
|
+
extra_rdoc_files: []
|
134
|
+
files:
|
135
|
+
- ".gitignore"
|
136
|
+
- ".rspec"
|
137
|
+
- ".rubocop.yml"
|
138
|
+
- ".travis.yml"
|
139
|
+
- CHANGELOG.md
|
140
|
+
- Gemfile
|
141
|
+
- LICENSE.txt
|
142
|
+
- README.md
|
143
|
+
- Rakefile
|
144
|
+
- lib/rhetor.rb
|
145
|
+
- lib/rhetor/exceptions.rb
|
146
|
+
- lib/rhetor/lexical_analyser.rb
|
147
|
+
- lib/rhetor/token.rb
|
148
|
+
- lib/rhetor/version.rb
|
149
|
+
- rhetor.gemspec
|
150
|
+
- spec/lexical_analyser_spec.rb
|
151
|
+
- spec/spec_helper.rb
|
152
|
+
- spec/support/coverage.rb
|
153
|
+
- spec/token_spec.rb
|
154
|
+
homepage: https://github.com/edubenetskiy/rhetor/
|
155
|
+
licenses:
|
156
|
+
- MIT
|
157
|
+
metadata: {}
|
158
|
+
post_install_message:
|
159
|
+
rdoc_options: []
|
160
|
+
require_paths:
|
161
|
+
- lib
|
162
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
163
|
+
requirements:
|
164
|
+
- - ">="
|
165
|
+
- !ruby/object:Gem::Version
|
166
|
+
version: 1.9.3
|
167
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
168
|
+
requirements:
|
169
|
+
- - ">="
|
170
|
+
- !ruby/object:Gem::Version
|
171
|
+
version: '0'
|
172
|
+
requirements: []
|
173
|
+
rubyforge_project:
|
174
|
+
rubygems_version: 2.6.14
|
175
|
+
signing_key:
|
176
|
+
specification_version: 4
|
177
|
+
summary: Rhetor is yet another lexical analyser written in Ruby
|
178
|
+
test_files:
|
179
|
+
- spec/lexical_analyser_spec.rb
|
180
|
+
- spec/spec_helper.rb
|
181
|
+
- spec/support/coverage.rb
|
182
|
+
- spec/token_spec.rb
|