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 +4 -4
- data/README.md +30 -7
- data/lib/dolos/parser_state.rb +3 -1
- data/lib/dolos/parsers.rb +19 -7
- data/lib/dolos/result.rb +25 -6
- data/lib/dolos/string_io_wrapper.rb +0 -1
- data/lib/dolos/version.rb +1 -1
- data/lib/dolos.rb +17 -5
- data/sig/dolos/parser_state.rbs +1 -0
- data/sig/dolos/result.rbs +1 -0
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c8a83a078e5fa82aa8f6d28f3315597937b84c604d59e18f177a950594860e93
|
4
|
+
data.tar.gz: bccf5fc9390189e43ef9c785baeaf93b85e00d1ce480c63057cf0cc26c468050
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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.
|
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.
|
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,
|
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,
|
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/) })
|
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.
|
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.
|
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.
|
data/lib/dolos/parser_state.rb
CHANGED
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 #{
|
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(
|
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(
|
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, :
|
43
|
+
attr_reader :message, :error_position, :state
|
44
44
|
|
45
|
-
def initialize(message,
|
45
|
+
def initialize(message, error_position, state)
|
46
46
|
@message = message
|
47
|
-
@
|
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
|
-
|
54
|
-
"
|
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
|
|
data/lib/dolos/version.rb
CHANGED
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
|
-
|
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
|
-
|
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
|
-
|
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(
|
140
|
+
Success.new(values, 0, captures)
|
129
141
|
end
|
130
142
|
end
|
131
143
|
end
|
data/sig/dolos/parser_state.rbs
CHANGED
data/sig/dolos/result.rbs
CHANGED
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.
|
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-
|
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.
|