dolos 0.1.1 → 0.1.3

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 95e38957665c1a69da17f65d7aad2a63f93e4d0ca4130acb55b60bc037a64947
4
- data.tar.gz: 8f715a942a8ef9e2edff94362b29624a9b053f90bc7fae72095161739cbd415a
3
+ metadata.gz: c8a83a078e5fa82aa8f6d28f3315597937b84c604d59e18f177a950594860e93
4
+ data.tar.gz: bccf5fc9390189e43ef9c785baeaf93b85e00d1ce480c63057cf0cc26c468050
5
5
  SHA512:
6
- metadata.gz: c7857e955041ace2291d005ad8ebab84d2c93c175c7364813b91a7753e1e6683af0458abf668cd9c5b628d02e74b9281c471e8568f746a87ec3fe5b211059ba1
7
- data.tar.gz: 4bc5adb33d601c88aa51af48dc611a2e7d20d9aaaf41919dbb0e83849641644be824e7369fa1e56c9fad7d74af30a5a551f33800a0aa8c633221e46692ac1113
6
+ metadata.gz: 75fcc5acf6a6dfbd4eeaf7493f9f82a3d31b5d6b61c64f2d9ee3f6f7fe8856fa1392866cbde95ba12bdb06155f07ed7c9cfe46c24f4353c111bf3782f6033289
7
+ data.tar.gz: '0608ddcb140a18c1d156ae8729f4c998b1abe4b257be8e02dddff796a3e7d861892ddfebcc6f54b744e9b03d381ed029ad81954b2ff484cc2621d5b7f804d3cb'
data/README.md CHANGED
@@ -11,15 +11,24 @@
11
11
  It does not use exceptions and instead returns a result object.
12
12
  Library is composable and concise.
13
13
 
14
+ ### Getting started
15
+
16
+ #### Installation
17
+ - Update Gemfile with `gem 'dolos'`
18
+ - Run bundle install
19
+
20
+ #### Usage
14
21
  ```ruby
22
+ require 'dolos'
15
23
  include Dolos
16
24
 
25
+ ws = c(" ")
17
26
  parser = c("Parsers") >> ws >> c("are") >> ws >> c("great!")
18
- parser.parse("Parsers are great!") # <Result::Success>
27
+ parser.run("Parsers are great!") # <Result::Success>
19
28
 
20
29
  greeter = c("Hello")
21
30
  greet_and_speak = greeter >> c(", ") >> parser
22
- greet_and_speak.parse("Hello, Parsers are great!") # <Result::Success>
31
+ greet_and_speak.run("Hello, Parsers are great!") # <Result::Success>
23
32
  ```
24
33
 
25
34
  ### Letter address parser example
@@ -30,12 +39,14 @@ require 'dolos_common_parsers/common_parsers'
30
39
  include Dolos
31
40
 
32
41
  # Include common parsers
33
- # In future this can be more structured, moved them to separate module to prevent breaking changes
42
+ # In future this can be more structured,
43
+ # moved them to separate module to prevent breaking changes
34
44
  include Dolos::CommonParsers
35
45
 
36
46
  # Library usage example
37
47
  # Parse out a name and address from a letter
38
- # For higher difficulty, we will not split this into multiple lines, but instead parse it all at once
48
+ # For higher difficulty, we will not split this into multiple lines,
49
+ # but instead parse it all at once
39
50
  letter = <<-LETTER
40
51
  Mr. Vardeniui Pavardeniui
41
52
  AB „Lietuvos Paštas“
@@ -77,23 +88,35 @@ second_line = ws.rep0 >> company_info >> eol
77
88
  # After that result is captured and mapped to hash
78
89
  # Mapping to hash so at the end its easy to tell tuples apart
79
90
  # Also while mapping, doing some cleaning with '.strip'
80
- street_name = char_while(->(char) { !char.match(/\d/) }).capture!.map(&:first).map { |s| { street: s.strip } }
91
+ street_name = char_while(->(char) { !char.match(/\d/) })
92
+ .capture!
93
+ .map(&:first)
94
+ .map { |s| { street: s.strip } }
81
95
  building = digits.capture!.map(&:first).map { |s| { building: s.strip } }
82
96
  address_line = ws.rep0 >> street_name >> building >> eol
83
97
 
84
98
  # City line
85
- # All digits can be matched here or 'digits.rep(5)' could be used. Also joining with map.
99
+ # All digits can be matched here or 'digits.rep(5)' could be used.
100
+ # Also joining with map results.
86
101
  postcode = digits.capture!.map(&:join).map { |s| { postcode: s.strip } }
87
102
  city = alpha_with_lt.rep.capture!.map(&:join).map { |s| { city: s.strip } }
88
103
  city_line = ws.rep0 >> postcode >> ws >> city >> eol
89
104
 
90
- # Full letter parser which is combined from all previous parsers. All previous parsers can be ran separately.
105
+ # Full letter parser which is combined from all previous parsers.
106
+ # Also, all previous parsers can be ran separately.
91
107
  letter_parser = name_line >> second_line >> address_line >> city_line
92
108
  result = letter_parser.run(letter)
93
109
 
110
+ # List of tuples
94
111
  pp result.captures
112
+ # ["Vardeniui", "Pavardeniui", "Lietuvos Paštas", {:street=>"Totorių g."},
113
+ # {:building=>"8"}, {:postcode=>"01121"}, {:city=>"Vilnius"}]
95
114
 
96
115
  ```
116
+ ### Roadmap
117
+ - Better error handling
118
+ - Recursive parser
119
+ - Benchmarks & parser tests
97
120
 
98
121
  ### Contributing
99
122
  Contributors are welcome. Note: since library is not yet stable, I recommend getting in touch with me before starting to work on something.
@@ -3,10 +3,12 @@
3
3
  module Dolos
4
4
  class ParserState
5
5
  attr_reader :input
6
+ attr_accessor :last_success_position
6
7
 
7
8
  def initialize(input)
8
9
  @input = StringIOWrapper.new(input)
10
+ @last_success_position = 0
9
11
  end
10
12
  end
11
-
12
13
  end
14
+
data/lib/dolos/parsers.rb CHANGED
@@ -10,14 +10,17 @@ module Dolos
10
10
  Success.new(utf8_str, str.bytesize)
11
11
  else
12
12
  advanced = state.input.offset
13
+ got_error = state.input.io.string.byteslice(state.input.backup, advanced)
13
14
  state.input.rollback
14
15
  Failure.new(
15
- "Expected #{str.inspect} but got #{state.input.io.string.inspect}",
16
- advanced
16
+ "Expected #{str.inspect} but got #{got_error.inspect}",
17
+ advanced,
18
+ state
17
19
  )
18
20
  end
19
21
  end
20
22
  end
23
+
21
24
  alias_method :c, :string
22
25
 
23
26
  def regex(pattern)
@@ -30,13 +33,13 @@ module Dolos
30
33
  state.input.rollback
31
34
  Failure.new(
32
35
  "Expected pattern #{pattern.inspect} but got #{state.input.io.string.inspect}",
33
- advanced
36
+ advanced,
37
+ state
34
38
  )
35
39
  end
36
40
  end
37
41
  end
38
42
 
39
-
40
43
  def any_char
41
44
  Parser.new do |state|
42
45
  state.input.mark_offset
@@ -48,7 +51,11 @@ module Dolos
48
51
  else
49
52
  advanced = state.input.offset
50
53
  state.input.rollback
51
- Failure.new('Expected any character but got end of input', advanced)
54
+ Failure.new(
55
+ 'Expected any character but got end of input',
56
+ advanced,
57
+ state
58
+ )
52
59
  end
53
60
  end
54
61
  end
@@ -71,7 +78,8 @@ module Dolos
71
78
  state.input.rollback
72
79
  Failure.new(
73
80
  "Expected one of #{characters_array.inspect} but got #{char.inspect}",
74
- advanced
81
+ advanced,
82
+ state
75
83
  )
76
84
  end
77
85
  end
@@ -92,7 +100,11 @@ module Dolos
92
100
 
93
101
  if buffer.empty?
94
102
  advanced = state.input.offset
95
- Failure.new("Predicate never returned true", advanced)
103
+ Failure.new(
104
+ "Predicate never returned true",
105
+ advanced,
106
+ state
107
+ )
96
108
  else
97
109
  Success.new(buffer, 0)
98
110
  end
data/lib/dolos/result.rb CHANGED
@@ -40,18 +40,37 @@ module Dolos
40
40
  end
41
41
 
42
42
  class Failure < Result
43
- attr_reader :message, :committed
43
+ attr_reader :message, :error_position, :state
44
44
 
45
- def initialize(message, committed)
45
+ def initialize(message, error_position, state)
46
46
  @message = message
47
- @committed = committed
47
+ @error_position = error_position
48
+ @state = state
48
49
  end
49
50
 
50
51
  def inspect
52
+ pretty_print
53
+ end
54
+
55
+ def pretty_print
56
+ input_string = state.input.io.string
57
+
58
+ pointer = "^" # This will point to the error position
59
+
60
+ context_range = 10 # Chars before and after the error to display
61
+
62
+ start_index = [error_position - context_range, 0].max
63
+ end_index = [error_position + context_range, input_string.length].max
64
+
65
+ substring = input_string[start_index..end_index]
66
+
67
+ padding = error_position - start_index
68
+
51
69
  [
52
- "Failure",
53
- "message: #{message}",
54
- "committed: #{committed}"
70
+ "Failure: #{message}",
71
+ substring,
72
+ "#{' ' * padding}#{pointer}",
73
+ "Error Position: #{error_position}, Last Success Position: #{state.last_success_position}"
55
74
  ].join("\n")
56
75
  end
57
76
 
@@ -22,7 +22,6 @@ module Dolos
22
22
 
23
23
  def matches?(utf8_str)
24
24
  read = io.read(utf8_str.bytesize)
25
- io.seek(offset)
26
25
 
27
26
  if read.nil?
28
27
  false
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.1"
4
+ VERSION = "0.1.3"
5
5
  end
data/lib/dolos.rb CHANGED
@@ -22,7 +22,11 @@ module Dolos
22
22
  end
23
23
 
24
24
  def run_with_state(state)
25
- parser_proc.call(state)
25
+ result = parser_proc.call(state)
26
+ if result.success?
27
+ state.last_success_position = state.input.offset
28
+ end
29
+ result
26
30
  end
27
31
 
28
32
  def capture!
@@ -109,23 +113,31 @@ module Dolos
109
113
  # repeat(n_min: 2) # 2 or more
110
114
  def repeat(n_min:, n_max: Float::INFINITY)
111
115
  Parser.new do |state|
112
- results = []
116
+ values = []
117
+ captures = []
113
118
  count = 0
119
+ state.input.mark_offset
114
120
 
115
121
  while count < n_max
116
122
  result = run_with_state(state.dup)
117
123
 
118
124
  break if result.failure?
119
125
 
120
- results << result.value
126
+ values << result.value
127
+ captures.concat(result.captures)
121
128
  state.input.advance(result.length)
122
129
  count += 1
123
130
  end
124
131
 
125
132
  if count < n_min
126
- Failure.new("Expected parser to match at least #{n_min} times but matched only #{count} times", false)
133
+ error_pos = state.input.offset
134
+ Failure.new(
135
+ "Expected parser to match at least #{n_min} times but matched only #{count} times",
136
+ error_pos,
137
+ state
138
+ )
127
139
  else
128
- Success.new(results, 0) # Passing 0, because we already advanced the input and flatmap will advance it again
140
+ Success.new(values, 0, captures)
129
141
  end
130
142
  end
131
143
  end
@@ -1,6 +1,7 @@
1
1
  module Dolos
2
2
  class ParserState
3
3
  attr_reader input: Dolos::StringIOWrapper
4
+ attr_accessor last_success_position: Integer
4
5
 
5
6
  def initialize: (String) -> void
6
7
  end
data/sig/dolos/result.rbs CHANGED
@@ -16,6 +16,7 @@ module Dolos
16
16
 
17
17
  class Failure < Result[bot]
18
18
  attr_reader committed: bool
19
+ attr_reader error_position: Integer
19
20
  attr_reader message: String
20
21
 
21
22
  def captures: -> []
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.1
4
+ version: 0.1.3
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-15 00:00:00.000000000 Z
11
+ date: 2023-08-17 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: Parser combinators library for Ruby. In active development, not stable
14
14
  yet.