srl_ruby 0.3.2 → 0.3.3
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/.rubocop.yml +29 -1
- data/CHANGELOG.md +10 -0
- data/Rakefile +1 -2
- data/bin/srl_ruby +41 -18
- data/lib/regex/char_range.rb +2 -2
- data/lib/regex/multiplicity.rb +69 -71
- data/lib/srl_ruby/ast_builder.rb +1 -1
- data/lib/srl_ruby/grammar.rb +5 -4
- data/lib/srl_ruby/tokenizer.rb +2 -2
- data/lib/srl_ruby/version.rb +1 -1
- data/lib/srl_ruby.rb +17 -12
- data/spec/acceptance/support/rule_file_nodes.rb +2 -2
- data/spec/regex/multiplicity_spec.rb +55 -57
- data/spec/spec_helper.rb +1 -1
- data/spec/srl_ruby/tokenizer_spec.rb +16 -16
- data/spec/srl_ruby_spec.rb +34 -34
- data/srl_ruby.gemspec +14 -12
- metadata +7 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5d359b7fdd648df4b9e77e0bf64266b3fec2d474
|
4
|
+
data.tar.gz: 7a8a1efb3f978b8f45b15c47c0b457a6109daf37
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 20c7d24104afefa0371692ca2f0713916b9a6edbc1825c32bb77ff1c29760fa46bd04731ab88914f8dbd1d97f1cbd523f8b5e765d57ad0cf9ea7938110161c95
|
7
|
+
data.tar.gz: 9110435c0ae7bf089c167a46c81f6aabe91a7f9d52926843654972877aef2ccc1838c5a5218e8eac1f628cf95328f1f044a62b9d1fc4cfbcc6db49752b24b9ee
|
data/.rubocop.yml
CHANGED
@@ -1,3 +1,31 @@
|
|
1
|
+
AllCops:
|
2
|
+
Exclude:
|
3
|
+
- 'exp/**/*'
|
4
|
+
|
1
5
|
Layout/EndOfLine:
|
2
6
|
Enabled: true
|
3
|
-
EnforcedStyle: lf
|
7
|
+
EnforcedStyle: lf
|
8
|
+
|
9
|
+
Metrics/BlockLength:
|
10
|
+
Enabled: true
|
11
|
+
Max: 300
|
12
|
+
|
13
|
+
Metrics/MethodLength:
|
14
|
+
Enabled: true
|
15
|
+
Max: 30
|
16
|
+
|
17
|
+
Metrics/ModuleLength:
|
18
|
+
Enabled: true
|
19
|
+
Max: 500
|
20
|
+
|
21
|
+
Naming/ConstantName:
|
22
|
+
Enabled: false
|
23
|
+
|
24
|
+
Naming/VariableName:
|
25
|
+
Enabled: false
|
26
|
+
|
27
|
+
Style/CommentedKeyword:
|
28
|
+
Enabled: false
|
29
|
+
|
30
|
+
Style/RedundantReturn:
|
31
|
+
Enabled: false
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,13 @@
|
|
1
|
+
## [0.3.3] - 2018-04-19
|
2
|
+
### Changed
|
3
|
+
- Binary `srl_ruby` now accepts a SRL filename as command-line argument. Command-line documentation updated.
|
4
|
+
|
5
|
+
### Fixed
|
6
|
+
- Method `Tokenizer#_next_token` failed to recognize single letter followed by a comma
|
7
|
+
- Binary `srl_ruby` now loads source files from correct filepath
|
8
|
+
- Module `SRL` is no more an ancestor of class `Multiplicity`.
|
9
|
+
|
10
|
+
|
1
11
|
## [0.3.2] - 2018-04-13
|
2
12
|
### Added
|
3
13
|
- `Cucumber` feature files are now part of the gem.
|
data/Rakefile
CHANGED
@@ -7,13 +7,12 @@ RSpec::Core::RakeTask.new do |spec|
|
|
7
7
|
spec.pattern = 'spec/**/*_spec.rb'
|
8
8
|
end
|
9
9
|
|
10
|
-
|
11
10
|
Cucumber::Rake::Task.new do |_|
|
12
11
|
end
|
13
12
|
|
14
13
|
# Combine RSpec and Cucumber tests
|
15
14
|
desc 'Run tests, with RSpec and Cucumber'
|
16
|
-
task test: [
|
15
|
+
task test: %i[spec cucumber]
|
17
16
|
|
18
17
|
# Default rake task
|
19
18
|
task default: :test
|
data/bin/srl_ruby
CHANGED
@@ -1,28 +1,51 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
|
-
require_relative '../lib/srl_ruby
|
2
|
+
require_relative '../lib/srl_ruby'
|
3
3
|
|
4
|
+
my_name = File.basename(__FILE__)
|
5
|
+
my_version = SrlRuby::VERSION
|
6
|
+
puts "#{my_name} v. #{my_version}"
|
4
7
|
|
5
8
|
# Parse the input expression in command-line
|
6
9
|
if ARGV.empty?
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
10
|
+
msg = <<-MSG
|
11
|
+
Simple Regex Language compiler:
|
12
|
+
- Parses an SRL expression and displays the corresponding Regexp literal
|
13
|
+
|
14
|
+
Command-line syntax (I):
|
15
|
+
========================
|
16
|
+
#{my_name} "SRL expression"
|
17
|
+
where:
|
18
|
+
the SRL expression is enclosed between double quotes (")
|
19
|
+
|
20
|
+
Examples:
|
21
|
+
#{my_name} "letter from a to f exactly 4 times"
|
22
|
+
#{my_name} "uppercase letter between 2 and 3 times"
|
23
|
+
#{my_name} "digit from 0 to 7 once or more"
|
24
|
+
|
25
|
+
Command-line syntax (II):
|
26
|
+
========================
|
27
|
+
#{my_name} -f filename
|
28
|
+
where:
|
29
|
+
the filename is a text file containing the SRL expression to compile
|
30
|
+
|
31
|
+
Examples:
|
32
|
+
#{my_name} -f hexadecimal.srl
|
33
|
+
MSG
|
22
34
|
puts msg
|
23
35
|
exit(1)
|
24
36
|
end
|
25
|
-
|
26
|
-
|
37
|
+
|
38
|
+
|
39
|
+
if ARGV[0] =~ /^--?fi?l?e?$/
|
40
|
+
srl_file = ARGV[1]
|
41
|
+
result = SrlRuby.load_file(srl_file)
|
42
|
+
else
|
43
|
+
srl_expr = ARGV[0]
|
44
|
+
puts 'SRL input: ' + srl_expr
|
45
|
+
result = SrlRuby.parse(srl_expr)
|
46
|
+
end
|
47
|
+
|
48
|
+
puts 'Resulting Regexp: /' + result.to_s + '/'
|
49
|
+
|
27
50
|
|
28
51
|
# End of file
|
data/lib/regex/char_range.rb
CHANGED
@@ -7,9 +7,9 @@ module Regex # This module is used as a namespace
|
|
7
7
|
# Assumption: characters are ordered by codepoint
|
8
8
|
class CharRange < PolyadicExpression
|
9
9
|
# Constructor.
|
10
|
-
# [thelowerBound]
|
10
|
+
# [thelowerBound]
|
11
11
|
# A character that will be the lower bound value for the range.
|
12
|
-
# [theUpperBound]
|
12
|
+
# [theUpperBound]
|
13
13
|
# A character that will be the upper bound value for the range.
|
14
14
|
# TODO: optimisation. Build a Character if lower bound == upper bound.
|
15
15
|
def initialize(theLowerBound, theUpperBound)
|
data/lib/regex/multiplicity.rb
CHANGED
@@ -1,91 +1,89 @@
|
|
1
1
|
# File: Multiplicity.rb
|
2
2
|
|
3
|
-
module
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
attr_reader(:lower_bound)
|
3
|
+
module Regex # This module is used as a namespace
|
4
|
+
# The multiplicity specifies by how much a given expression can be repeated.
|
5
|
+
class Multiplicity
|
6
|
+
# The lowest acceptable repetition count
|
7
|
+
attr_reader(:lower_bound)
|
9
8
|
|
10
|
-
|
11
|
-
|
9
|
+
# The highest possible repetition count
|
10
|
+
attr_reader(:upper_bound)
|
12
11
|
|
13
|
-
|
14
|
-
|
12
|
+
# An indicator that specifies how to repeat (:greedy, :lazy, :possessive)
|
13
|
+
attr_reader(:policy)
|
15
14
|
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
# Purpose: Return the String representation of the multiplicity.
|
26
|
-
def to_str()
|
27
|
-
case upper_bound
|
28
|
-
when :more
|
29
|
-
case lower_bound
|
30
|
-
when 0
|
31
|
-
subresult = '*'
|
32
|
-
when 1
|
33
|
-
subresult = '+'
|
34
|
-
else
|
35
|
-
subresult = "{#{lower_bound},}"
|
36
|
-
end
|
15
|
+
# @param aLowerBound [Integer]
|
16
|
+
# @param anUpperBound [Integer, Symbol] integer or :more symbol
|
17
|
+
# @param aPolicy [Symbol] One of: (:greedy, :lazy, :possessive)
|
18
|
+
def initialize(aLowerBound, anUpperBound, aPolicy)
|
19
|
+
@lower_bound = valid_lower_bound(aLowerBound)
|
20
|
+
@upper_bound = valid_upper_bound(anUpperBound)
|
21
|
+
@policy = valid_policy(aPolicy)
|
22
|
+
end
|
37
23
|
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
24
|
+
# Purpose: Return the String representation of the multiplicity.
|
25
|
+
def to_str()
|
26
|
+
case upper_bound
|
27
|
+
when :more
|
28
|
+
case lower_bound
|
29
|
+
when 0
|
30
|
+
subresult = '*'
|
31
|
+
when 1
|
32
|
+
subresult = '+'
|
43
33
|
else
|
44
|
-
subresult = "{#{lower_bound}
|
45
|
-
|
46
|
-
end
|
34
|
+
subresult = "{#{lower_bound},}"
|
35
|
+
end
|
47
36
|
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
'?'
|
53
|
-
|
54
|
-
|
55
|
-
|
37
|
+
when lower_bound
|
38
|
+
subresult = "{#{lower_bound}}"
|
39
|
+
else
|
40
|
+
if [lower_bound, upper_bound] == [0, 1]
|
41
|
+
subresult = '?'
|
42
|
+
else
|
43
|
+
subresult = "{#{lower_bound},#{upper_bound}}"
|
44
|
+
end
|
45
|
+
end
|
56
46
|
|
57
|
-
|
47
|
+
suffix = case policy
|
48
|
+
when :greedy
|
49
|
+
''
|
50
|
+
when :lazy
|
51
|
+
'?'
|
52
|
+
when :possessive
|
53
|
+
'+'
|
58
54
|
end
|
59
55
|
|
60
|
-
|
56
|
+
return subresult + suffix
|
57
|
+
end
|
61
58
|
|
62
|
-
|
63
|
-
def valid_lower_bound(aLowerBound)
|
64
|
-
err_msg = "Invalid lower bound of repetition count #{aLowerBound}"
|
65
|
-
raise StandardError, err_msg unless aLowerBound.kind_of?(Integer)
|
66
|
-
return aLowerBound
|
67
|
-
end
|
59
|
+
private
|
68
60
|
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
61
|
+
# Validation method. Return the validated lower bound value
|
62
|
+
def valid_lower_bound(aLowerBound)
|
63
|
+
err_msg = "Invalid lower bound of repetition count #{aLowerBound}"
|
64
|
+
raise StandardError, err_msg unless aLowerBound.kind_of?(Integer)
|
65
|
+
return aLowerBound
|
66
|
+
end
|
75
67
|
|
76
|
-
|
68
|
+
# Validation method. Return the validated lower bound value
|
69
|
+
def valid_upper_bound(anUpperBound)
|
70
|
+
err_msg = "Invalid upper bound of repetition count #{anUpperBound}"
|
71
|
+
unless anUpperBound.kind_of?(Integer) || (anUpperBound == :more)
|
72
|
+
raise StandardError, err_msg
|
77
73
|
end
|
78
74
|
|
79
|
-
|
80
|
-
|
81
|
-
err_msg = "Invalid repetition policy '#{aPolicy}'."
|
82
|
-
valid_policies = %i[greedy lazy possessive]
|
83
|
-
raise StandardError, err_msg unless valid_policies.include? aPolicy
|
75
|
+
return anUpperBound
|
76
|
+
end
|
84
77
|
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
78
|
+
# Validation method. Return the validated policy value.
|
79
|
+
def valid_policy(aPolicy)
|
80
|
+
err_msg = "Invalid repetition policy '#{aPolicy}'."
|
81
|
+
valid_policies = %i[greedy lazy possessive]
|
82
|
+
raise StandardError, err_msg unless valid_policies.include? aPolicy
|
83
|
+
|
84
|
+
return aPolicy
|
85
|
+
end
|
86
|
+
end # class
|
89
87
|
end # module
|
90
88
|
|
91
89
|
# End of file
|
data/lib/srl_ruby/ast_builder.rb
CHANGED
@@ -34,7 +34,7 @@ module SrlRuby
|
|
34
34
|
end
|
35
35
|
|
36
36
|
def multiplicity(lowerBound, upperBound)
|
37
|
-
return
|
37
|
+
return Regex::Multiplicity.new(lowerBound, upperBound, :greedy)
|
38
38
|
end
|
39
39
|
|
40
40
|
def string_literal(aString, to_escape = true)
|
data/lib/srl_ruby/grammar.rb
CHANGED
@@ -6,11 +6,11 @@ module SrlRuby
|
|
6
6
|
builder = Rley::Syntax::GrammarBuilder.new do
|
7
7
|
# Separators...
|
8
8
|
add_terminals('LPAREN', 'RPAREN', 'COMMA')
|
9
|
-
|
9
|
+
|
10
10
|
# Literal values...
|
11
11
|
add_terminals('DIGIT_LIT', 'INTEGER', 'LETTER_LIT', 'CHAR_CLASS')
|
12
12
|
add_terminals('LITERALLY', 'STRING_LIT', 'IDENTIFIER')
|
13
|
-
|
13
|
+
|
14
14
|
# Keywords...
|
15
15
|
add_terminals('BEGIN', 'STARTS', 'WITH')
|
16
16
|
add_terminals('MUST', 'END', 'RAW')
|
@@ -74,8 +74,8 @@ module SrlRuby
|
|
74
74
|
rule('digit_range' => %w[digit_or_number FROM DIGIT_LIT TO DIGIT_LIT]).as 'digits_from_to'
|
75
75
|
rule('character_class' => %w[ANY CHARACTER]).as 'any_character'
|
76
76
|
rule('character_class' => %w[NO CHARACTER]).as 'no_character'
|
77
|
-
rule('character_class' => 'digit_or_number').as 'digit'
|
78
|
-
rule('character_class' => %w[NO DIGIT]).as 'non_digit'
|
77
|
+
rule('character_class' => 'digit_or_number').as 'digit'
|
78
|
+
rule('character_class' => %w[NO DIGIT]).as 'non_digit'
|
79
79
|
rule('character_class' => 'WHITESPACE').as 'whitespace'
|
80
80
|
rule('character_class' => %w[NO WHITESPACE]).as 'no_whitespace'
|
81
81
|
rule('character_class' => 'ANYTHING').as 'anything'
|
@@ -123,5 +123,6 @@ module SrlRuby
|
|
123
123
|
end
|
124
124
|
|
125
125
|
# And now build the grammar and make it accessible via a global constant
|
126
|
+
# [Rley::Syntax::Grammar]
|
126
127
|
Grammar = builder.grammar
|
127
128
|
end # module
|
data/lib/srl_ruby/tokenizer.rb
CHANGED
@@ -124,7 +124,7 @@ module SrlRuby
|
|
124
124
|
elsif (lexeme = scanner.scan(/'(?:\\'|[^'])*'/)) # Single quotes literal?
|
125
125
|
unquoted = lexeme.gsub(/(^')|('$)/, '')
|
126
126
|
token = build_token('STRING_LIT', unquoted)
|
127
|
-
elsif (lexeme = scanner.scan(/[a-zA-Z]((?=\s)|$)/))
|
127
|
+
elsif (lexeme = scanner.scan(/[a-zA-Z]((?=\s|,)|$)/))
|
128
128
|
token = build_token('LETTER_LIT', lexeme)
|
129
129
|
elsif (lexeme = scanner.scan(/[a-zA-Z_][a-zA-Z0-9_]+/))
|
130
130
|
keyw = @@keywords[lexeme.upcase]
|
@@ -133,7 +133,7 @@ module SrlRuby
|
|
133
133
|
elsif (lexeme = scanner.scan(/[^,"\s]{2,}/))
|
134
134
|
token = build_token('CHAR_CLASS', lexeme)
|
135
135
|
else # Unknown token
|
136
|
-
erroneous = curr_ch.nil? ? '' :
|
136
|
+
erroneous = curr_ch.nil? ? '' : scanner.scan(/./)
|
137
137
|
sequel = scanner.scan(/.{1,20}/)
|
138
138
|
erroneous += sequel unless sequel.nil?
|
139
139
|
raise ScanError.new("Unknown token #{erroneous} on line #{lineno}")
|
data/lib/srl_ruby/version.rb
CHANGED
data/lib/srl_ruby.rb
CHANGED
@@ -3,22 +3,29 @@ require_relative './srl_ruby/tokenizer'
|
|
3
3
|
require_relative './srl_ruby/grammar'
|
4
4
|
require_relative './srl_ruby/ast_builder'
|
5
5
|
|
6
|
-
module SrlRuby
|
7
|
-
#
|
8
|
-
#
|
9
|
-
# @
|
10
|
-
# @return [Regexp]
|
6
|
+
module SrlRuby
|
7
|
+
# Compile the SRL expression in given filename into a Regexp object.
|
8
|
+
# @param filename [String] Name of SRL file to parse
|
9
|
+
# @return [Regexp] A regexp object equivalent to the SRL expression.
|
11
10
|
def self.load_file(filename)
|
12
11
|
source = nil
|
13
12
|
File.open(filename, 'r') { |f| source = f.read }
|
14
13
|
return source if source.nil? || source.empty?
|
15
|
-
|
14
|
+
|
16
15
|
return parse(source)
|
17
16
|
end
|
18
|
-
|
19
|
-
#
|
20
|
-
# @param source [String]
|
21
|
-
# @return [Regexp]
|
17
|
+
|
18
|
+
# Compile the given SRL expression into its Regexp equivalent.
|
19
|
+
# @param source [String] SRL expression to parse
|
20
|
+
# @return [Regexp] A regexp object equivalent to the SRL expression.
|
21
|
+
# @example Matching a (signed) integer literal
|
22
|
+
# some_srl =<<-SRL
|
23
|
+
# begin with
|
24
|
+
# (one of "+-") optional,
|
25
|
+
# digit once or more,
|
26
|
+
# must end
|
27
|
+
# SRL
|
28
|
+
# regex = SrlRuby::API.parse(some_srl) # => regex == /^[+\-]?\d+$/
|
22
29
|
def self.parse(source)
|
23
30
|
# Create a Rley facade object
|
24
31
|
engine = Rley::Engine.new { |cfg| cfg.diagnose = true }
|
@@ -42,8 +49,6 @@ module SrlRuby # This module is used as a namespace
|
|
42
49
|
|
43
50
|
# Now output the regexp literal
|
44
51
|
root = ast_ptree.root
|
45
|
-
# puts root.to_str # TODO remove this line
|
46
|
-
# pp root
|
47
52
|
return Regexp.new(root.to_str)
|
48
53
|
end
|
49
54
|
end # module
|
@@ -37,9 +37,9 @@ module Acceptance
|
|
37
37
|
self.srl = aSRLExpression.dup
|
38
38
|
self.match_tests = []
|
39
39
|
self.no_match_tests = []
|
40
|
-
|
40
|
+
self.capture_tests = []
|
41
41
|
end
|
42
|
-
|
42
|
+
end
|
43
43
|
|
44
44
|
MatchTest = Struct.new(:test_string)
|
45
45
|
NoMatchTest = Struct.new(:test_string)
|
@@ -3,77 +3,75 @@
|
|
3
3
|
require_relative '../spec_helper' # Use the RSpec test framework
|
4
4
|
require_relative '../../lib/regex/multiplicity'
|
5
5
|
|
6
|
-
module
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
expect { Multiplicity.new(0, 1, aPolicy) }.not_to raise_error
|
15
|
-
end
|
16
|
-
|
17
|
-
# Invalid case: initialized with invalid policy value
|
18
|
-
err = StandardError
|
19
|
-
msg = "Invalid repetition policy 'KO'."
|
20
|
-
expect { Multiplicity.new(0, :more, 'KO') }.to raise_error(err, msg)
|
6
|
+
# Reopen the module, in order to get rid of fully qualified names
|
7
|
+
module Regex # This module is used as a namespace
|
8
|
+
describe Multiplicity do
|
9
|
+
context 'Creation & initialisation' do
|
10
|
+
it 'should be created with 3 arguments' do
|
11
|
+
# Valid cases: initialized with two integer values and a policy symbol
|
12
|
+
%i[greedy lazy possessive].each do |aPolicy|
|
13
|
+
expect { Multiplicity.new(0, 1, aPolicy) }.not_to raise_error
|
21
14
|
end
|
15
|
+
|
16
|
+
# Invalid case: initialized with invalid policy value
|
17
|
+
err = StandardError
|
18
|
+
msg = "Invalid repetition policy 'KO'."
|
19
|
+
expect { Multiplicity.new(0, :more, 'KO') }.to raise_error(err, msg)
|
22
20
|
end
|
21
|
+
end
|
23
22
|
|
24
|
-
|
25
|
-
|
26
|
-
|
23
|
+
context 'Provided services' do
|
24
|
+
it 'should know its text representation' do
|
25
|
+
policy2text = { greedy: '', lazy: '?', possessive: '+' }
|
27
26
|
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
27
|
+
# Case: zero or one
|
28
|
+
policy2text.each_key do |aPolicy|
|
29
|
+
multi = Multiplicity.new(0, 1, aPolicy)
|
30
|
+
expect(multi.to_str).to eq("?#{policy2text[aPolicy]}")
|
31
|
+
end
|
33
32
|
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
33
|
+
# Case: zero or more
|
34
|
+
policy2text.each_key do |aPolicy|
|
35
|
+
multi = Multiplicity.new(0, :more, aPolicy)
|
36
|
+
expect(multi.to_str).to eq("*#{policy2text[aPolicy]}")
|
37
|
+
end
|
39
38
|
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
39
|
+
# Case: one or more
|
40
|
+
policy2text.each_key do |aPolicy|
|
41
|
+
multi = Multiplicity.new(1, :more, aPolicy)
|
42
|
+
expect(multi.to_str).to eq("+#{policy2text[aPolicy]}")
|
43
|
+
end
|
45
44
|
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
end
|
45
|
+
# Case: exactly m times
|
46
|
+
policy2text.each_key do |aPolicy|
|
47
|
+
samples = [1, 2, 5, 100]
|
48
|
+
samples.each do |aCount|
|
49
|
+
multi = Multiplicity.new(aCount, aCount, aPolicy)
|
50
|
+
expect(multi.to_str).to eq("{#{aCount}}#{policy2text[aPolicy]}")
|
53
51
|
end
|
52
|
+
end
|
54
53
|
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
end
|
54
|
+
# Case: m, n times
|
55
|
+
policy2text.each_key do |aPolicy|
|
56
|
+
samples = [1, 2, 5, 100]
|
57
|
+
samples.each do |aCount|
|
58
|
+
upper = aCount + 1 + rand(20)
|
59
|
+
multi = Multiplicity.new(aCount, upper, aPolicy)
|
60
|
+
expectation = "{#{aCount},#{upper}}#{policy2text[aPolicy]}"
|
61
|
+
expect(multi.to_str).to eq(expectation)
|
64
62
|
end
|
63
|
+
end
|
65
64
|
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
end
|
65
|
+
# Case: m or more
|
66
|
+
policy2text.each_key do |aPolicy|
|
67
|
+
samples = [2, 3, 5, 100]
|
68
|
+
samples.each do |aCount|
|
69
|
+
multi = Multiplicity.new(aCount, :more, aPolicy)
|
70
|
+
expect(multi.to_str).to eq("{#{aCount},}#{policy2text[aPolicy]}")
|
73
71
|
end
|
74
72
|
end
|
75
73
|
end
|
76
74
|
end
|
77
|
-
end
|
75
|
+
end
|
78
76
|
end # module
|
79
77
|
# End of file
|
data/spec/spec_helper.rb
CHANGED
@@ -5,7 +5,7 @@ require_relative '../lib/srl_ruby'
|
|
5
5
|
RSpec.configure do |config|
|
6
6
|
# Enable flags like --only-failures and --next-failure
|
7
7
|
config.example_status_persistence_file_path = '.rspec_status'
|
8
|
-
|
8
|
+
|
9
9
|
config.expect_with :rspec do |c|
|
10
10
|
# Disable the `should` syntax...
|
11
11
|
c.syntax = :expect
|
@@ -10,9 +10,9 @@ module SrlRuby
|
|
10
10
|
expect(token.lexeme).to eq(lexeme)
|
11
11
|
end
|
12
12
|
end
|
13
|
-
|
14
|
-
subject { Tokenizer.new('') }
|
15
|
-
|
13
|
+
|
14
|
+
subject { Tokenizer.new('') }
|
15
|
+
|
16
16
|
context 'Initialization:' do
|
17
17
|
it 'should be initialized with a text to tokenize and a grammar' do
|
18
18
|
expect { Tokenizer.new('anything') }.not_to raise_error
|
@@ -31,7 +31,7 @@ module SrlRuby
|
|
31
31
|
expect(token.terminal).to eq('COMMA')
|
32
32
|
expect(token.lexeme).to eq(',')
|
33
33
|
end
|
34
|
-
|
34
|
+
|
35
35
|
it 'should tokenize keywords' do
|
36
36
|
sample = 'between Exactly oncE optional TWICE'
|
37
37
|
subject.scanner.string = sample
|
@@ -40,14 +40,14 @@ module SrlRuby
|
|
40
40
|
expect(tok.terminal).to eq(tok.lexeme.upcase)
|
41
41
|
end
|
42
42
|
end
|
43
|
-
|
43
|
+
|
44
44
|
it 'should tokenize integer values' do
|
45
45
|
subject.scanner.string = ' 123 '
|
46
46
|
token = subject.tokens.first
|
47
47
|
expect(token).to be_kind_of(Rley::Lexical::Token)
|
48
48
|
expect(token.terminal).to eq('INTEGER')
|
49
49
|
expect(token.lexeme).to eq('123')
|
50
|
-
end
|
50
|
+
end
|
51
51
|
|
52
52
|
it 'should tokenize single digits' do
|
53
53
|
subject.scanner.string = ' 1 '
|
@@ -55,9 +55,9 @@ module SrlRuby
|
|
55
55
|
expect(token).to be_kind_of(Rley::Lexical::Token)
|
56
56
|
expect(token.terminal).to eq('DIGIT_LIT')
|
57
57
|
expect(token.lexeme).to eq('1')
|
58
|
-
end
|
58
|
+
end
|
59
59
|
end # context
|
60
|
-
|
60
|
+
|
61
61
|
context 'String literal tokenization:' do
|
62
62
|
it "should recognize 'literally ...'" do
|
63
63
|
input = 'literally "hello"'
|
@@ -67,9 +67,9 @@ module SrlRuby
|
|
67
67
|
%w[STRING_LIT hello]
|
68
68
|
]
|
69
69
|
match_expectations(subject, expectations)
|
70
|
-
end
|
71
|
-
end # context
|
72
|
-
|
70
|
+
end
|
71
|
+
end # context
|
72
|
+
|
73
73
|
context 'Character range tokenization:' do
|
74
74
|
it "should recognize 'letter from ... to ...'" do
|
75
75
|
input = 'letter a to f'
|
@@ -81,9 +81,9 @@ module SrlRuby
|
|
81
81
|
%w[LETTER_LIT f]
|
82
82
|
]
|
83
83
|
match_expectations(subject, expectations)
|
84
|
-
end
|
84
|
+
end
|
85
85
|
end # context
|
86
|
-
|
86
|
+
|
87
87
|
context 'Quantifier tokenization:' do
|
88
88
|
it "should recognize 'exactly ... times'" do
|
89
89
|
input = 'exactly 4 Times'
|
@@ -95,7 +95,7 @@ module SrlRuby
|
|
95
95
|
]
|
96
96
|
match_expectations(subject, expectations)
|
97
97
|
end
|
98
|
-
|
98
|
+
|
99
99
|
it "should recognize 'between ... and ... times'" do
|
100
100
|
input = 'Between 2 AND 4 times'
|
101
101
|
subject.scanner.string = input
|
@@ -129,7 +129,7 @@ module SrlRuby
|
|
129
129
|
%w[MORE more]
|
130
130
|
]
|
131
131
|
match_expectations(subject, expectations)
|
132
|
-
end
|
132
|
+
end
|
133
133
|
|
134
134
|
it "should recognize 'at least ... times'" do
|
135
135
|
input = 'at least 10 times'
|
@@ -141,7 +141,7 @@ module SrlRuby
|
|
141
141
|
%w[TIMES times]
|
142
142
|
]
|
143
143
|
match_expectations(subject, expectations)
|
144
|
-
end
|
144
|
+
end
|
145
145
|
end # context
|
146
146
|
end # describe
|
147
147
|
end # module
|
data/spec/srl_ruby_spec.rb
CHANGED
@@ -44,7 +44,7 @@ describe SrlRuby do
|
|
44
44
|
regexp = SrlRuby.parse("literally '.'")
|
45
45
|
expect(regexp.source).to eq('\.')
|
46
46
|
end
|
47
|
-
|
47
|
+
|
48
48
|
it 'should parse single quotes literal string' do
|
49
49
|
regexp = SrlRuby.parse('literally "an", whitespace, raw "[a-zA-Z]"')
|
50
50
|
expect(regexp.source).to eq('an\s[a-zA-Z]')
|
@@ -61,7 +61,7 @@ describe SrlRuby do
|
|
61
61
|
regexp = SrlRuby.parse('number')
|
62
62
|
expect(regexp.source).to eq('\d')
|
63
63
|
end
|
64
|
-
|
64
|
+
|
65
65
|
it "should parse 'no digit' syntax" do
|
66
66
|
regexp = SrlRuby.parse('no digit')
|
67
67
|
expect(regexp.source).to eq('\D')
|
@@ -98,41 +98,41 @@ describe SrlRuby do
|
|
98
98
|
# (escapes more characters than required)
|
99
99
|
expect(regexp.source).to eq('[._%+\-]')
|
100
100
|
end
|
101
|
-
|
101
|
+
|
102
102
|
it "should parse 'one of' with unquoted character class syntax" do
|
103
103
|
# Case of digit sequence
|
104
104
|
regexp = SrlRuby.parse('one of 13579, must end')
|
105
105
|
expect(regexp.source).to eq('[13579]$')
|
106
|
-
|
106
|
+
|
107
107
|
# Case of identifier-like character class
|
108
108
|
regexp = SrlRuby.parse('one of abcd, must end')
|
109
109
|
expect(regexp.source).to eq('[abcd]$')
|
110
110
|
|
111
111
|
# Case of arbitrary character class
|
112
112
|
regexp = SrlRuby.parse('one of 12hms:, must end')
|
113
|
-
expect(regexp.source).to eq('[12hms:]$')
|
114
|
-
end
|
115
|
-
|
113
|
+
expect(regexp.source).to eq('[12hms:]$')
|
114
|
+
end
|
115
|
+
|
116
116
|
it "should parse 'none of' syntax" do
|
117
117
|
regexp = SrlRuby.parse('none of "._%+-"')
|
118
118
|
# Remark: reference implementation less readable
|
119
119
|
# (escapes more characters than required)
|
120
120
|
expect(regexp.source).to eq('[^._%+\-]')
|
121
|
-
end
|
121
|
+
end
|
122
122
|
|
123
123
|
it "should parse 'none of' with unquoted character class syntax" do
|
124
124
|
# Case of digit sequence
|
125
125
|
regexp = SrlRuby.parse('none of 13579, must end')
|
126
126
|
expect(regexp.source).to eq('[^13579]$')
|
127
|
-
|
127
|
+
|
128
128
|
# Case of identifier-like character class
|
129
129
|
regexp = SrlRuby.parse('none of abcd, must end')
|
130
130
|
expect(regexp.source).to eq('[^abcd]$')
|
131
131
|
|
132
132
|
# Case of arbitrary character class
|
133
133
|
regexp = SrlRuby.parse('none of 12hms:^, must end')
|
134
|
-
expect(regexp.source).to eq('[^12hms:\^]$')
|
135
|
-
end
|
134
|
+
expect(regexp.source).to eq('[^12hms:\^]$')
|
135
|
+
end
|
136
136
|
end # context
|
137
137
|
|
138
138
|
context 'Parsing special character declarations:' do
|
@@ -140,7 +140,7 @@ describe SrlRuby do
|
|
140
140
|
regexp = SrlRuby.parse('tab')
|
141
141
|
expect(regexp.source).to eq('\t')
|
142
142
|
end
|
143
|
-
|
143
|
+
|
144
144
|
it "should parse 'vertical tab' syntax" do
|
145
145
|
regexp = SrlRuby.parse('vertical tab')
|
146
146
|
expect(regexp.source).to eq('\v')
|
@@ -155,21 +155,21 @@ describe SrlRuby do
|
|
155
155
|
regexp = SrlRuby.parse('new line')
|
156
156
|
expect(regexp.source).to eq('\n')
|
157
157
|
end
|
158
|
-
|
158
|
+
|
159
159
|
it "should parse 'carriage return' syntax" do
|
160
160
|
regexp = SrlRuby.parse('carriage return')
|
161
161
|
expect(regexp.source).to eq('\r')
|
162
|
-
end
|
162
|
+
end
|
163
163
|
|
164
164
|
it "should parse 'word' syntax" do
|
165
165
|
regexp = SrlRuby.parse('word, literally "is"')
|
166
166
|
expect(regexp.source).to eq('\bis')
|
167
|
-
end
|
167
|
+
end
|
168
168
|
|
169
169
|
it "should parse 'no word' syntax" do
|
170
170
|
regexp = SrlRuby.parse('no word, literally "is"')
|
171
171
|
expect(regexp.source).to eq('\Bis')
|
172
|
-
end
|
172
|
+
end
|
173
173
|
end # context
|
174
174
|
|
175
175
|
context 'Parsing alternations:' do
|
@@ -177,25 +177,25 @@ describe SrlRuby do
|
|
177
177
|
source = 'any of (any character, one of "._%-+")'
|
178
178
|
regexp = SrlRuby.parse(source)
|
179
179
|
expect(regexp.source).to eq('(?:\w|[._%\-+])')
|
180
|
-
end
|
180
|
+
end
|
181
181
|
|
182
182
|
it "should parse 'either of' syntax" do
|
183
183
|
source = 'either of (any character, one of "._%-+")'
|
184
184
|
regexp = SrlRuby.parse(source)
|
185
185
|
expect(regexp.source).to eq('(?:\w|[._%\-+])')
|
186
|
-
end
|
187
|
-
|
186
|
+
end
|
187
|
+
|
188
188
|
it 'should anchor as alternative' do
|
189
189
|
regexp = SrlRuby.parse('any of (literally "?", must end)')
|
190
190
|
expect(regexp.source).to eq('(?:\\?|$)')
|
191
|
-
end
|
191
|
+
end
|
192
192
|
end # context
|
193
193
|
|
194
194
|
context 'Parsing concatenation:' do
|
195
195
|
it 'should reject dangling comma' do
|
196
196
|
source = 'literally "a",'
|
197
197
|
err = StandardError
|
198
|
-
msg_pattern = /Premature end of input after ',' at position line 1, column 14/
|
198
|
+
msg_pattern = /Premature end of input after ',' at position line 1, column 14/
|
199
199
|
expect { SrlRuby.parse(source) }.to raise_error(err, msg_pattern)
|
200
200
|
end
|
201
201
|
|
@@ -205,13 +205,13 @@ describe SrlRuby do
|
|
205
205
|
end
|
206
206
|
|
207
207
|
it 'should parse a long sequence of patterns' do
|
208
|
-
source = <<-
|
208
|
+
source = <<-SRL
|
209
209
|
any of (any character, one of "._%-+") once or more,
|
210
210
|
literally "@",
|
211
211
|
any of (digit, letter, one of ".-") once or more,
|
212
212
|
literally ".",
|
213
213
|
letter at least 2 times
|
214
|
-
|
214
|
+
SRL
|
215
215
|
|
216
216
|
regexp = SrlRuby.parse(source)
|
217
217
|
# SRL: (?:\w|[\._%\-\+])+(?:@)(?:[0-9]|[a-z]|[\.\-])+(?:\.)[a-z]{2,}
|
@@ -246,9 +246,9 @@ END_SRL
|
|
246
246
|
it "should parse 'between ... and ... times' syntax" do
|
247
247
|
regexp = SrlRuby.parse(prefix + 'between 2 and 4 times')
|
248
248
|
expect(regexp.source).to eq('[p-t]{2,4}')
|
249
|
-
|
249
|
+
|
250
250
|
# Dropping 'times' keyword is a shorter alternative syntax
|
251
|
-
regexp = SrlRuby.parse(prefix + 'between 2 and 4')
|
251
|
+
regexp = SrlRuby.parse(prefix + 'between 2 and 4')
|
252
252
|
expect(regexp.source).to eq('[p-t]{2,4}')
|
253
253
|
end
|
254
254
|
|
@@ -312,22 +312,22 @@ END_SRL
|
|
312
312
|
regexp = SrlRuby.parse(source)
|
313
313
|
expect(regexp.source).to eq('(?<first>.+)$')
|
314
314
|
end
|
315
|
-
|
315
|
+
|
316
316
|
it 'should parse complex named capturing group' do
|
317
|
-
source = <<-
|
318
|
-
capture(any of (literally "sample", (digit once or more)))
|
317
|
+
source = <<-SRL
|
318
|
+
capture(any of (literally "sample", (digit once or more)))
|
319
319
|
as "foo"
|
320
|
-
|
320
|
+
SRL
|
321
321
|
regexp = SrlRuby.parse(source)
|
322
322
|
expect(regexp.source).to eq('(?<foo>(?:sample|(?:\d+)))')
|
323
323
|
end
|
324
324
|
|
325
325
|
it 'should parse a sequence with named capturing groups' do
|
326
|
-
source = <<-
|
326
|
+
source = <<-SRL
|
327
327
|
capture (anything once or more) as "first",
|
328
328
|
literally " - ",
|
329
329
|
capture literally "second part" as "second"
|
330
|
-
|
330
|
+
SRL
|
331
331
|
regexp = SrlRuby.parse(source)
|
332
332
|
expect(regexp.source).to eq('(?<first>.+) - (?<second>second part)')
|
333
333
|
end
|
@@ -361,16 +361,16 @@ END_SRL
|
|
361
361
|
end
|
362
362
|
|
363
363
|
it 'should accept anchor with a sequence of patterns' do
|
364
|
-
source = <<-
|
364
|
+
source = <<-SRL
|
365
365
|
begin with any of (digit, letter, one of ".-") once or more,
|
366
366
|
literally ".",
|
367
367
|
letter at least 2 times must end
|
368
|
-
|
368
|
+
SRL
|
369
369
|
|
370
370
|
regexp = SrlRuby.parse(source)
|
371
371
|
# SRL: (?:\w|[\._%\-\+])+(?:@)(?:[0-9]|[a-z]|[\.\-])+(?:\.)[a-z]{2,}
|
372
372
|
expect(regexp.source).to eq('^(?:\d|[a-z]|[.\-])+\.[a-z]{2,}$')
|
373
|
-
end
|
373
|
+
end
|
374
374
|
end # context
|
375
375
|
end # describe
|
376
376
|
# End of file
|
data/srl_ruby.gemspec
CHANGED
@@ -1,8 +1,8 @@
|
|
1
|
-
|
2
1
|
lib = File.expand_path('../lib', __FILE__)
|
3
2
|
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
3
|
require 'srl_ruby/version'
|
5
4
|
|
5
|
+
# Implementation module
|
6
6
|
module PkgExtending
|
7
7
|
def self.pkg_files(aPackage)
|
8
8
|
file_list = Dir[
|
@@ -42,29 +42,31 @@ Gem::Specification.new do |spec|
|
|
42
42
|
spec.authors = ['Dimitri Geshef']
|
43
43
|
spec.email = ['famished.tiger@yahoo.com']
|
44
44
|
|
45
|
-
spec.description =
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
45
|
+
spec.description = <<-DESCR
|
46
|
+
Build easily regular expressions thanks to Simple Regex Language
|
47
|
+
DESCR
|
48
|
+
spec.summary = <<-SUMMARY
|
49
|
+
A parser for the [Simple Regex Language](https://simple-regex.com/).
|
50
|
+
It translates patterns expressed in SRL into plain Ruby Regexp objects
|
51
|
+
or in regex literals. Use self-documenting, human readable, SRL expressions
|
52
|
+
to craft your new awesome regular expressions.
|
53
|
+
SUMMARY
|
52
54
|
spec.homepage = 'https://github.com/famished-tiger/SRL-Ruby'
|
53
55
|
spec.license = 'MIT'
|
54
56
|
|
55
57
|
spec.bindir = 'bin'
|
56
58
|
spec.executables << 'srl_ruby'
|
57
59
|
spec.require_paths = ['lib']
|
58
|
-
PkgExtending
|
59
|
-
PkgExtending
|
60
|
+
PkgExtending.pkg_files(spec)
|
61
|
+
PkgExtending.pkg_documentation(spec)
|
60
62
|
spec.required_ruby_version = '>= 2.1.0'
|
61
|
-
|
63
|
+
|
62
64
|
# Runtime dependencies
|
63
65
|
spec.add_dependency 'rley', '~> 0.6.06'
|
64
66
|
|
65
67
|
# Development dependencies
|
66
68
|
spec.add_development_dependency 'bundler', '~> 1.16'
|
67
|
-
spec.add_development_dependency 'cucumber', '>= 2.2.0'
|
69
|
+
spec.add_development_dependency 'cucumber', '>= 2.2.0'
|
68
70
|
spec.add_development_dependency 'rake', '~> 10.0'
|
69
71
|
spec.add_development_dependency 'rspec', '~> 3.0'
|
70
72
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: srl_ruby
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.3.
|
4
|
+
version: 0.3.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Dimitri Geshef
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2018-04-
|
11
|
+
date: 2018-04-19 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rley
|
@@ -80,7 +80,8 @@ dependencies:
|
|
80
80
|
- - "~>"
|
81
81
|
- !ruby/object:Gem::Version
|
82
82
|
version: '3.0'
|
83
|
-
description:
|
83
|
+
description: |2
|
84
|
+
Build easily regular expressions thanks to Simple Regex Language
|
84
85
|
email:
|
85
86
|
- famished.tiger@yahoo.com
|
86
87
|
executables:
|
@@ -202,9 +203,9 @@ rubygems_version: 2.6.13
|
|
202
203
|
signing_key:
|
203
204
|
specification_version: 4
|
204
205
|
summary: A parser for the [Simple Regex Language](https://simple-regex.com/). It translates
|
205
|
-
patterns expressed in SRL into plain Ruby Regexp objects
|
206
|
-
|
207
|
-
|
206
|
+
patterns expressed in SRL into plain Ruby Regexp objects or in regex literals. Use
|
207
|
+
self-documenting, human readable, SRL expressions to craft your new awesome regular
|
208
|
+
expressions.
|
208
209
|
test_files:
|
209
210
|
- spec/acceptance/srl_test_suite_spec.rb
|
210
211
|
- spec/regex/character_spec.rb
|