dolos 0.1.3 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
  require_relative 'dolos'
3
- require_relative 'dolos_common_parsers/common_parsers'
3
+ require_relative 'dolos_common_parsers/arsers/common_parsers'
4
4
 
5
5
  include Dolos
6
6
 
@@ -27,12 +27,12 @@ alpha_with_lt = char_in("ąčęėįšųūžĄČĘĖĮŠŲŪŽ") | alpha
27
27
 
28
28
  # Capture all letters in a row and join them,
29
29
  # because they are captured as elements of array by each alpha_with_lt parser.
30
- first_name = alpha_with_lt.rep.capture!.map(&:join)
31
- last_name = alpha_with_lt.rep.capture!.map(&:join)
30
+ first_name = alpha_with_lt.rep.map(&:join).capture!
31
+ last_name = alpha_with_lt.rep.map(&:join).capture!
32
32
 
33
33
  # Combine first line parsers
34
34
  # Consume zero or more whitespace, after that honorific must follow and so on
35
- name_line = ws.rep0 >> honorific >> first_name >> ws >> last_name >> eol
35
+ name_line = ws.rep0 & honorific & first_name & ws & last_name & eol
36
36
 
37
37
  # Next line is company info
38
38
  # We could choose to accept UAB and AB or just AB and etc.
@@ -42,9 +42,9 @@ quote_open = c("„")
42
42
  quote_close = c("“")
43
43
 
44
44
  # Consume LT alphabet with whitespace
45
- company_name = (alpha_with_lt | ws).rep.capture!.map(&:join)
46
- company_info = company_type >> ws.rep0 >> quote_open >> company_name >> quote_close
47
- second_line = ws.rep0 >> company_info >> eol
45
+ company_name = (alpha_with_lt | ws).rep.map(&:join).capture!
46
+ company_info = company_type & ws.rep0 & quote_open & company_name & quote_close
47
+ second_line = ws.rep0 & company_info & eol
48
48
 
49
49
  # Address line
50
50
  # 'char_while' will consume characters while passed predicate is true
@@ -52,18 +52,18 @@ second_line = ws.rep0 >> company_info >> eol
52
52
  # After that result is captured and mapped to hash
53
53
  # Mapping to hash so at the end its easy to tell tuples apart
54
54
  # Also while mapping, doing some cleaning with '.strip'
55
- street_name = char_while(->(char) { !char.match(/\d/) }).capture!.map(&:first).map { |s| { street: s.strip } }
56
- building = digits.capture!.map(&:first).map { |s| { building: s.strip } }
57
- address_line = ws.rep0 >> street_name >> building >> eol
55
+ street_name = char_while(->(char) { !char.match(/\d/) }).map { |s| { street: s.strip } }.capture!
56
+ building = digits.map { |s| { building: s.strip } }.capture!
57
+ address_line = ws.rep0 & street_name & building & eol
58
58
 
59
59
  # City line
60
60
  # All digits can be matched here or 'digits.rep(5)' could be used. Also joining with map.
61
- postcode = digits.capture!.map(&:join).map { |s| { postcode: s.strip } }
62
- city = alpha_with_lt.rep.capture!.map(&:join).map { |s| { city: s.strip } }
63
- city_line = ws.rep0 >> postcode >> ws >> city >> eol
61
+ postcode = digits.map { |s| { postcode: s.strip } }.capture!
62
+ city = alpha_with_lt.rep.map(&:join).map { |s| { city: s.strip } }.capture!
63
+ city_line = ws.rep0 & postcode & ws & city & eol
64
64
 
65
65
  # Full letter parser which is combined from all previous parsers. All previous parsers can be ran separately.
66
- letter_parser = name_line >> second_line >> address_line >> city_line
66
+ letter_parser = name_line & second_line & address_line & city_line
67
67
  result = letter_parser.run(letter)
68
68
 
69
69
  pp result.captures
data/lib/dolos/parsers.rb CHANGED
@@ -111,5 +111,26 @@ module Dolos
111
111
  end
112
112
  end
113
113
 
114
+ # Unstable API
115
+ def recursive(&block)
116
+ recursive_parser = nil
117
+
118
+ placeholder = Parser.new do |state|
119
+ raise "Recursive parser accessed before it was initialized!" if recursive_parser.nil?
120
+
121
+ recursive_parser.call.run_with_state(state).tap do |result|
122
+ if result.failure?
123
+ error_msg = "Error in recursive structure around position #{state.input.offset}: #{result.message}"
124
+ Failure.new(error_msg, state.input.offset, state)
125
+ end
126
+ end
127
+ end
128
+
129
+ recursive_parser = -> { block.call(placeholder) }
130
+ placeholder
131
+ end
132
+
133
+
134
+
114
135
  end
115
136
  end
data/lib/dolos/result.rb CHANGED
@@ -10,20 +10,21 @@ module Dolos
10
10
  def initialize(value, length, captures = [])
11
11
  @value = value
12
12
  @length = length
13
- # @captures = captures || value
14
13
  @captures = captures
15
14
  end
16
15
 
17
- def capture!
18
- if value.is_a?(Array)
19
- value.each do |v|
20
- captures << v
21
- end
16
+ # can be some named capture, :street, {:street => capture }
17
+ # or an array, [], [capture]
18
+ def capture!(wrap_in = nil)
19
+ mapped_value = self.value # use the transformed value here
20
+
21
+ if wrap_in.is_a?(Array)
22
+ save_capture([mapped_value])
23
+ elsif wrap_in.is_a?(Symbol)
24
+ save_capture({ wrap_in => mapped_value })
22
25
  else
23
- captures << value
26
+ save_capture(mapped_value)
24
27
  end
25
-
26
- Success.new(value, length, captures)
27
28
  end
28
29
 
29
30
  def inspect
@@ -37,6 +38,20 @@ module Dolos
37
38
  def failure?
38
39
  false
39
40
  end
41
+
42
+ private
43
+
44
+ def save_capture(val)
45
+ if val.is_a?(Array)
46
+ val.each do |v|
47
+ captures << v
48
+ end
49
+ else
50
+ captures << val
51
+ end
52
+
53
+ Success.new(val, length, captures)
54
+ end
40
55
  end
41
56
 
42
57
  class Failure < Result
data/lib/dolos/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Dolos
4
- VERSION = "0.1.3"
4
+ VERSION = "0.2.0"
5
5
  end
data/lib/dolos.rb CHANGED
@@ -29,18 +29,19 @@ module Dolos
29
29
  result
30
30
  end
31
31
 
32
- def capture!
32
+ def capture!(wrap_in = nil)
33
33
  Parser.new do |state|
34
34
  result = run_with_state(state)
35
35
  if result.success?
36
- result.capture!
36
+ result.capture!(wrap_in)
37
37
  else
38
38
  result
39
39
  end
40
40
  end
41
41
  end
42
42
 
43
- def map(&block)
43
+ # Will call block on captures
44
+ def map_captures(&block)
44
45
  Parser.new do |state|
45
46
  result = run_with_state(state)
46
47
  if result.success?
@@ -51,7 +52,8 @@ module Dolos
51
52
  end
52
53
  end
53
54
 
54
- def map_value(&block)
55
+ # Will call block on tuple of value
56
+ def map(&block)
55
57
  Parser.new do |state|
56
58
  result = run_with_state(state)
57
59
  if result.success?
@@ -62,7 +64,7 @@ module Dolos
62
64
  end
63
65
  end
64
66
 
65
- def flat_map(&block)
67
+ def combine(&block)
66
68
  Parser.new do |state|
67
69
  result = run_with_state(state)
68
70
  if result.success?
@@ -77,22 +79,44 @@ module Dolos
77
79
  end
78
80
 
79
81
  def flatten
80
- map do |captures|
82
+ map_captures do |captures|
81
83
  captures.flatten
82
84
  end
83
85
  end
84
86
 
85
87
  def product(other_parser)
86
- flat_map do |value1, capture1|
87
- other_parser.map_value do |value2|
88
+ combine do |value1, capture1|
89
+ other_parser.map do |value2|
88
90
  [value1, value2]
89
- end.map do |capture2|
91
+ end.map_captures do |capture2|
92
+ [capture1, capture2].flatten
93
+ end
94
+ end
95
+ end
96
+ alias_method :&, :product
97
+
98
+ def product_l(other_parser)
99
+ combine do |value1, capture1|
100
+ other_parser.map do |_|
101
+ value1
102
+ end.map_captures do |capture2|
103
+ [capture1, capture2].flatten
104
+ end
105
+ end
106
+ end
107
+
108
+ def product_r(other_parser)
109
+ combine do |_, capture1|
110
+ other_parser.map do |value2|
111
+ value2
112
+ end.map_captures do |capture2|
90
113
  [capture1, capture2].flatten
91
114
  end
92
115
  end
93
116
  end
94
117
 
95
- alias_method :>>, :product
118
+ alias_method :<<, :product_l
119
+ alias_method :>>, :product_r
96
120
 
97
121
  def choice(other_parser)
98
122
  Parser.new do |state|
@@ -111,22 +135,31 @@ module Dolos
111
135
  # rep(n = 2) # exactly 2
112
136
  # repeat(n_min: 2, n_max: 4) # 2 to 4
113
137
  # repeat(n_min: 2) # 2 or more
114
- def repeat(n_min:, n_max: Float::INFINITY)
138
+ def repeat(n_min:, n_max: Float::INFINITY, separator: nil)
115
139
  Parser.new do |state|
116
140
  values = []
117
141
  captures = []
118
142
  count = 0
119
143
  state.input.mark_offset
120
144
 
121
- while count < n_max
145
+ loop do
122
146
  result = run_with_state(state.dup)
123
147
 
124
- break if result.failure?
148
+ if result.failure? || count >= n_max
149
+ break
150
+ end
125
151
 
126
152
  values << result.value
127
153
  captures.concat(result.captures)
128
154
  state.input.advance(result.length)
129
155
  count += 1
156
+
157
+ if separator && count < n_max
158
+ sep_result = separator.run_with_state(state.dup)
159
+ break if sep_result.failure?
160
+
161
+ state.input.advance(sep_result.length)
162
+ end
130
163
  end
131
164
 
132
165
  if count < n_min
@@ -168,5 +201,22 @@ module Dolos
168
201
  end
169
202
  alias_method :opt, :optional
170
203
 
204
+ # Unstable API
205
+ # Used to declare lazy parser to avoid infinite loops in recursive parsers
206
+ def lazy
207
+ parser_memo = nil
208
+
209
+ Parser.new do |state|
210
+ parser_memo ||= self
211
+ parser_memo.run_with_state(state)
212
+ end
213
+ end
214
+
215
+ private
216
+
217
+ def combine_and_discard_empty(*arrays)
218
+ arrays.compact.reject { |arr| arr.is_a?(Array) && arr.empty? }
219
+ end
220
+
171
221
  end
172
222
  end
@@ -10,9 +10,12 @@ module Dolos
10
10
  regex(/\n|\r\n|\r/)
11
11
  end
12
12
 
13
- # Capture as String and convert to integer
14
13
  def digit
15
- regex(/\d/).capture!.map { |capt| capt.map(&:to_i) }
14
+ regex(/\d/)
15
+ end
16
+
17
+ def int
18
+ digit.map(&:to_i)
16
19
  end
17
20
 
18
21
  # Capture as string
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dolos
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.3
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - benetis
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2023-08-17 00:00:00.000000000 Z
11
+ date: 2023-08-19 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: Parser combinators library for Ruby. In active development, not stable
14
14
  yet.
@@ -23,7 +23,10 @@ files:
23
23
  - LICENSE.txt
24
24
  - README.md
25
25
  - Rakefile
26
+ - benchmarks/json/json.rb
27
+ - benchmarks/json/nested_json.json
26
28
  - docs/dolos_stable_diff.png
29
+ - examples/letter.rb
27
30
  - lib/dolos.rb
28
31
  - lib/dolos/parser_state.rb
29
32
  - lib/dolos/parsers.rb
@@ -31,7 +34,6 @@ files:
31
34
  - lib/dolos/string_io_wrapper.rb
32
35
  - lib/dolos/version.rb
33
36
  - lib/dolos_common_parsers/common_parsers.rb
34
- - lib/example.rb
35
37
  - sig/dolos.rbs
36
38
  - sig/dolos/common_parsers.rbs
37
39
  - sig/dolos/parser.rbs