dolos 0.1.1 → 0.1.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 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.