drudge 0.4.0
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 +7 -0
- data/.gitignore +18 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +59 -0
- data/LICENSE.txt +22 -0
- data/README.md +107 -0
- data/Rakefile +27 -0
- data/drudge.gemspec +35 -0
- data/features/optional-arguments.feature +64 -0
- data/features/simple-commands.feature +185 -0
- data/features/step_definitions/scripts_steps.rb +19 -0
- data/features/support/env.rb +5 -0
- data/features/variable-length-argument-lists.feature +111 -0
- data/lib/drudge.rb +8 -0
- data/lib/drudge/class_dsl.rb +106 -0
- data/lib/drudge/command.rb +100 -0
- data/lib/drudge/dispatch.rb +41 -0
- data/lib/drudge/errors.rb +30 -0
- data/lib/drudge/ext.rb +17 -0
- data/lib/drudge/kit.rb +45 -0
- data/lib/drudge/parsers.rb +91 -0
- data/lib/drudge/parsers/parse_results.rb +254 -0
- data/lib/drudge/parsers/primitives.rb +278 -0
- data/lib/drudge/parsers/tokenizer.rb +70 -0
- data/lib/drudge/version.rb +3 -0
- data/spec/drudge/class_dsl_spec.rb +125 -0
- data/spec/drudge/command_spec.rb +81 -0
- data/spec/drudge/kit_spec.rb +50 -0
- data/spec/drudge/parsers/parse_results_spec.rb +47 -0
- data/spec/drudge/parsers/primitives_spec.rb +262 -0
- data/spec/drudge/parsers/tokenizer_spec.rb +71 -0
- data/spec/drudge/parsers_spec.rb +149 -0
- data/spec/spec_helper.rb +2 -0
- data/spec/support/capture.rb +16 -0
- data/spec/support/fixtures.rb +13 -0
- data/spec/support/parser_matchers.rb +42 -0
- metadata +219 -0
data/lib/drudge/kit.rb
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
require 'drudge/errors'
|
2
|
+
require 'drudge/parsers'
|
3
|
+
require 'drudge/command'
|
4
|
+
|
5
|
+
class Drudge
|
6
|
+
|
7
|
+
# A kit is a set of commands that can be dispatched to
|
8
|
+
class Kit
|
9
|
+
include Parsers
|
10
|
+
|
11
|
+
# the name of the kit
|
12
|
+
attr_accessor :name
|
13
|
+
|
14
|
+
# the list of commands the kit hsa
|
15
|
+
attr_accessor :commands
|
16
|
+
|
17
|
+
|
18
|
+
def initialize(name, commands = [])
|
19
|
+
@name, @commands = name.to_sym, commands
|
20
|
+
end
|
21
|
+
|
22
|
+
# Dispatches a command within the kit
|
23
|
+
# The first argument is the command name
|
24
|
+
def dispatch(command_name, *args)
|
25
|
+
command = find_command(command_name) rescue nil
|
26
|
+
|
27
|
+
raise UnknownCommandError.new(name), "A command is required" unless command
|
28
|
+
|
29
|
+
command.dispatch *args
|
30
|
+
|
31
|
+
end
|
32
|
+
|
33
|
+
# returns the argument parser for this kit
|
34
|
+
def argument_parser
|
35
|
+
commands.map { |c| (command(name) > command(c.name) > commit(c.argument_parser)).collated_arguments }
|
36
|
+
.reduce { |p1, p2| p1 | p2 }
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
def find_command(command_name)
|
42
|
+
commands.find { |c| c.name == command_name.to_sym }
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
require 'drudge/errors'
|
2
|
+
require 'drudge/parsers/tokenizer'
|
3
|
+
require 'drudge/parsers/primitives'
|
4
|
+
|
5
|
+
class Drudge
|
6
|
+
|
7
|
+
module Parsers
|
8
|
+
include Primitives
|
9
|
+
|
10
|
+
# returns a parser that matches a :val on the input
|
11
|
+
# +expected+ is compared to the input using === (i.e. you can use it as a matcher for
|
12
|
+
# all sorts of things)
|
13
|
+
def value(expected = /.*/,
|
14
|
+
eos_failure_msg: "expected a value",
|
15
|
+
failure_msg: -> ((_, value)) { "'#{value}' doesn't match #{expected}" })
|
16
|
+
|
17
|
+
accept(-> ((kind, value)) { kind == :val && expected === value },
|
18
|
+
eos_failure_msg: eos_failure_msg,
|
19
|
+
failure_msg: failure_msg )
|
20
|
+
.mapv { |_, value| value }
|
21
|
+
.describe expected.to_s
|
22
|
+
|
23
|
+
end
|
24
|
+
|
25
|
+
# matches the end of the stream
|
26
|
+
def eos(message = "Expected end-of-command")
|
27
|
+
super
|
28
|
+
end
|
29
|
+
|
30
|
+
# parses a single argument with the provided name
|
31
|
+
def arg(name, expected = value(/.*/))
|
32
|
+
expected.mapv { |a| [:arg, a] }
|
33
|
+
.with_failure_message { |msg| "#{msg} for <#{name}>" }
|
34
|
+
.describe "<#{name}>"
|
35
|
+
end
|
36
|
+
|
37
|
+
# parses a command
|
38
|
+
def command(name)
|
39
|
+
value(name.to_s, eos_failure_msg: "expected a command",
|
40
|
+
failure_msg: -> ((_, val)) { "unknown command '#{val}'" })
|
41
|
+
.mapv { |v| [:arg, v] }
|
42
|
+
.describe(name.to_s)
|
43
|
+
end
|
44
|
+
|
45
|
+
def parser_mixin
|
46
|
+
ArgumentParser
|
47
|
+
end
|
48
|
+
|
49
|
+
module ArgumentParser
|
50
|
+
include Primitives::Parser
|
51
|
+
include Tokenizer
|
52
|
+
include Parsers
|
53
|
+
|
54
|
+
# tokenizes and parses an array of arguments
|
55
|
+
def parse(argv)
|
56
|
+
self[tokenize(argv)]
|
57
|
+
end
|
58
|
+
|
59
|
+
# tokenizes and parses an array of arguments, returning the
|
60
|
+
# parsed result. Raises an error if the parse fails
|
61
|
+
def parse!(argv)
|
62
|
+
input = tokenize(argv)
|
63
|
+
res = self[input]
|
64
|
+
|
65
|
+
if res.success?
|
66
|
+
res.result
|
67
|
+
else
|
68
|
+
raise ParseError.new(input, res.remaining), res.message
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
# a parser that collates the results of argument parsing
|
73
|
+
def collated_arguments
|
74
|
+
self.mapr do |results|
|
75
|
+
args = results.to_a.reduce({args: []}) do |a, (kind, value, *rest)|
|
76
|
+
case kind
|
77
|
+
when :arg
|
78
|
+
a[:args] << value
|
79
|
+
end
|
80
|
+
|
81
|
+
a
|
82
|
+
end
|
83
|
+
|
84
|
+
Single(args)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
end
|
89
|
+
|
90
|
+
end
|
91
|
+
end
|
@@ -0,0 +1,254 @@
|
|
1
|
+
class Drudge
|
2
|
+
module Parsers
|
3
|
+
|
4
|
+
# Classes representing parse results
|
5
|
+
module ParseResults
|
6
|
+
|
7
|
+
module FactoryMethods
|
8
|
+
# helper methods for constructing
|
9
|
+
|
10
|
+
def Success(value, remaining)
|
11
|
+
Success.new(value, remaining)
|
12
|
+
end
|
13
|
+
|
14
|
+
def Failure(message, remaining)
|
15
|
+
Failure.new(message, remaining)
|
16
|
+
end
|
17
|
+
|
18
|
+
def Error(message, remaining)
|
19
|
+
Error.new(message, remaining)
|
20
|
+
end
|
21
|
+
|
22
|
+
def Empty()
|
23
|
+
Empty.new
|
24
|
+
end
|
25
|
+
|
26
|
+
def Single(value)
|
27
|
+
Single.new(value)
|
28
|
+
end
|
29
|
+
|
30
|
+
def Seq(arr)
|
31
|
+
Seq.new(arr)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
include FactoryMethods
|
36
|
+
|
37
|
+
# Identifies a parse result. It can be a Success or NotSuccess
|
38
|
+
module ParseResult
|
39
|
+
# applies the provided block to the containing ParseValue
|
40
|
+
# returns a new ParseResult containing the modified value
|
41
|
+
def map ; end
|
42
|
+
|
43
|
+
# applies the provied block to the contained parse value
|
44
|
+
def map_in_parse_value; end
|
45
|
+
|
46
|
+
# monadic bind (or flat_map) of two sequential results
|
47
|
+
def flat_map; end
|
48
|
+
|
49
|
+
def flat_map_with_next(&parser_producer); end
|
50
|
+
|
51
|
+
# Combines this result with the other by applying the '+' operator
|
52
|
+
# on the underlying ParseValue
|
53
|
+
# takes care of failure / success combinatorics are observed
|
54
|
+
def +(other)
|
55
|
+
self.flat_map do |res|
|
56
|
+
other.map do |other_res|
|
57
|
+
res + other_res
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
# returns true if the ParseResult was successful
|
63
|
+
def success?; end
|
64
|
+
end
|
65
|
+
|
66
|
+
# A successful parse result. Contains a ParseValue as
|
67
|
+
# the parse_result attr.
|
68
|
+
Success = Struct.new(:parse_result, :remaining) do
|
69
|
+
include ParseResult
|
70
|
+
|
71
|
+
def map
|
72
|
+
self.class.new(yield(parse_result), remaining)
|
73
|
+
end
|
74
|
+
|
75
|
+
def map_in_parse_value(&f)
|
76
|
+
self.class.new(parse_result.map(&f), remaining)
|
77
|
+
end
|
78
|
+
|
79
|
+
def flat_map
|
80
|
+
yield parse_result
|
81
|
+
end
|
82
|
+
|
83
|
+
def flat_map_with_next(&next_parser_producer)
|
84
|
+
next_parser_producer[self][remaining]
|
85
|
+
end
|
86
|
+
|
87
|
+
def success?
|
88
|
+
true
|
89
|
+
end
|
90
|
+
|
91
|
+
def empty?
|
92
|
+
Empty === parse_result
|
93
|
+
end
|
94
|
+
|
95
|
+
# fully unwraps the parse result
|
96
|
+
def result
|
97
|
+
if empty?
|
98
|
+
nil
|
99
|
+
else
|
100
|
+
parse_result.value
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
end
|
105
|
+
|
106
|
+
# an unsuccesful parse result
|
107
|
+
module NotSuccess
|
108
|
+
include ParseResult
|
109
|
+
|
110
|
+
def map
|
111
|
+
self
|
112
|
+
end
|
113
|
+
|
114
|
+
def map_in_parse_value
|
115
|
+
self
|
116
|
+
end
|
117
|
+
|
118
|
+
# propagate error
|
119
|
+
def flat_map
|
120
|
+
self
|
121
|
+
end
|
122
|
+
|
123
|
+
def flat_map_with_next(&_)
|
124
|
+
self
|
125
|
+
end
|
126
|
+
|
127
|
+
def success?
|
128
|
+
false
|
129
|
+
end
|
130
|
+
|
131
|
+
end
|
132
|
+
|
133
|
+
# A failure that allows for backtracking
|
134
|
+
Failure = Struct.new(:message, :remaining) do
|
135
|
+
include NotSuccess
|
136
|
+
end
|
137
|
+
|
138
|
+
# An error doesn't allow backtracking
|
139
|
+
Error = Struct.new(:message, :remaining) do
|
140
|
+
include NotSuccess
|
141
|
+
end
|
142
|
+
|
143
|
+
|
144
|
+
# A parse value is a wrapper around the values
|
145
|
+
# produced by parsers that allow for
|
146
|
+
# concatanations (monoidal)
|
147
|
+
module ParseValue
|
148
|
+
include FactoryMethods
|
149
|
+
|
150
|
+
# returns the 'zero' of the parse, value
|
151
|
+
def self.zero
|
152
|
+
Empty()
|
153
|
+
end
|
154
|
+
|
155
|
+
# Concatenation of parse values
|
156
|
+
def +(other); end
|
157
|
+
|
158
|
+
# converts the value into an array
|
159
|
+
def to_a; end
|
160
|
+
end
|
161
|
+
|
162
|
+
|
163
|
+
class Empty
|
164
|
+
include ParseValue
|
165
|
+
|
166
|
+
def map
|
167
|
+
self
|
168
|
+
end
|
169
|
+
|
170
|
+
def flat_map
|
171
|
+
self
|
172
|
+
end
|
173
|
+
|
174
|
+
def +(other)
|
175
|
+
other
|
176
|
+
end
|
177
|
+
|
178
|
+
def to_a
|
179
|
+
[]
|
180
|
+
end
|
181
|
+
|
182
|
+
def ==(other)
|
183
|
+
Empty === other
|
184
|
+
end
|
185
|
+
|
186
|
+
alias :eql? :==
|
187
|
+
end
|
188
|
+
|
189
|
+
Single = Struct.new(:value) do
|
190
|
+
include ParseValue
|
191
|
+
|
192
|
+
def map
|
193
|
+
self.class.new(yield value)
|
194
|
+
end
|
195
|
+
|
196
|
+
def flat_map
|
197
|
+
yield value
|
198
|
+
end
|
199
|
+
|
200
|
+
def +(other)
|
201
|
+
case other
|
202
|
+
|
203
|
+
when Empty
|
204
|
+
self
|
205
|
+
|
206
|
+
when Single
|
207
|
+
Seq([self.value, other.value])
|
208
|
+
|
209
|
+
when Seq
|
210
|
+
Seq([self.value] + other.value)
|
211
|
+
end
|
212
|
+
end
|
213
|
+
|
214
|
+
def to_a
|
215
|
+
[value]
|
216
|
+
end
|
217
|
+
|
218
|
+
end
|
219
|
+
|
220
|
+
# A sequence of succesful results
|
221
|
+
Seq = Struct.new(:value) do
|
222
|
+
include ParseValue
|
223
|
+
|
224
|
+
def map
|
225
|
+
self.class.new(yield value)
|
226
|
+
end
|
227
|
+
|
228
|
+
def flat_map
|
229
|
+
yield value
|
230
|
+
end
|
231
|
+
|
232
|
+
def +(other)
|
233
|
+
case other
|
234
|
+
|
235
|
+
when Empty
|
236
|
+
self
|
237
|
+
|
238
|
+
when Single
|
239
|
+
Seq(self.value + [other.value])
|
240
|
+
|
241
|
+
when Seq
|
242
|
+
Seq(self.value + other.value)
|
243
|
+
end
|
244
|
+
end
|
245
|
+
|
246
|
+
def to_a
|
247
|
+
value
|
248
|
+
end
|
249
|
+
end
|
250
|
+
|
251
|
+
end
|
252
|
+
|
253
|
+
end
|
254
|
+
end
|
@@ -0,0 +1,278 @@
|
|
1
|
+
require 'drudge/parsers/parse_results'
|
2
|
+
|
3
|
+
class Drudge
|
4
|
+
module Parsers
|
5
|
+
|
6
|
+
# Parser primitives
|
7
|
+
module Primitives
|
8
|
+
include ParseResults
|
9
|
+
|
10
|
+
# a method that helps bulding parsers
|
11
|
+
# converts a block into a parser
|
12
|
+
def parser(&prs)
|
13
|
+
prs.singleton_class.send :include, parser_mixin
|
14
|
+
prs
|
15
|
+
end
|
16
|
+
|
17
|
+
# produces a parser that expects the +expected+ value
|
18
|
+
# +expected+ can is checked using '===' so
|
19
|
+
# accept(String) -> will accept any string
|
20
|
+
# accept(2) -> will accept 2
|
21
|
+
# accept(-> v { v / 2 == 4 }) will use the lambda to check the value
|
22
|
+
# accept { |v| v / 2 == 4 } is also possible
|
23
|
+
def accept(expected = nil,
|
24
|
+
eos_failure_msg: "expected a #{expected}",
|
25
|
+
failure_msg: -> value { "'#{value}' doesn't match #{expected}" },
|
26
|
+
&expected_block)
|
27
|
+
|
28
|
+
expected = expected_block if expected_block
|
29
|
+
|
30
|
+
to_message = -> (value, msg) do
|
31
|
+
case msg
|
32
|
+
when String
|
33
|
+
msg
|
34
|
+
when Proc
|
35
|
+
msg[value]
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
parser do |input|
|
40
|
+
value, *rest = input
|
41
|
+
|
42
|
+
case
|
43
|
+
when input.nil? || input.empty?
|
44
|
+
Failure(to_message[value, eos_failure_msg], input)
|
45
|
+
when expected === value
|
46
|
+
Success(Single(value), rest)
|
47
|
+
else
|
48
|
+
Failure(to_message[value, failure_msg], input)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
# returns a parser that always succeeds with the provided ParseValue
|
54
|
+
def success(parse_value)
|
55
|
+
parser { |input| Success(parse_value, input) }.describe "SUCC: #{parse_value}"
|
56
|
+
end
|
57
|
+
|
58
|
+
# matches the end of the stream
|
59
|
+
def eos(message)
|
60
|
+
parser do |input|
|
61
|
+
if input.empty?
|
62
|
+
Success(Empty(), [])
|
63
|
+
else
|
64
|
+
Failure(message, input)
|
65
|
+
end
|
66
|
+
end.describe ""
|
67
|
+
end
|
68
|
+
|
69
|
+
# Commits the provided parser. If +prs+ returns
|
70
|
+
# a +Failure+, it will be converted into an +Error+ that
|
71
|
+
# will stop backtracking inside a '|' operator
|
72
|
+
def commit(prs)
|
73
|
+
parser do |input|
|
74
|
+
result = prs[input]
|
75
|
+
|
76
|
+
case result
|
77
|
+
when Success, Error
|
78
|
+
result
|
79
|
+
when Failure
|
80
|
+
Error(result.message, result.remaining)
|
81
|
+
end
|
82
|
+
end.describe(prs.to_s)
|
83
|
+
end
|
84
|
+
|
85
|
+
# Returns the module which is to be mixed in in every
|
86
|
+
# constructed parser. Can be overriden to mix in
|
87
|
+
# additonal features
|
88
|
+
def parser_mixin
|
89
|
+
Parser
|
90
|
+
end
|
91
|
+
protected :parser_mixin
|
92
|
+
|
93
|
+
# This modules contains the parser combinators
|
94
|
+
module Parser
|
95
|
+
include ParseResults
|
96
|
+
include Primitives
|
97
|
+
|
98
|
+
# Returns a new parser that applies the parser function to the
|
99
|
+
# parse result of self
|
100
|
+
def map(&f)
|
101
|
+
parser { |input| f[self[input]] }.describe(self.to_s)
|
102
|
+
end
|
103
|
+
|
104
|
+
# like map but yields the actual value of the parse result
|
105
|
+
# (ParseResult contains ParseValue which contains the actual value)
|
106
|
+
# wrapped value. Usefull if you want to know if the result was
|
107
|
+
# a sequence or not
|
108
|
+
def map_in_parse_value(&f)
|
109
|
+
parser { |input| self[input].map { |pr| pr.map &f } }.describe(self.to_s)
|
110
|
+
end
|
111
|
+
|
112
|
+
alias_method :mapv, :map_in_parse_value
|
113
|
+
|
114
|
+
# likemap but yields the the contents of the ParseResult instead of the vrapped value
|
115
|
+
def map_in_parse_result(&f)
|
116
|
+
parser { |input| self[input].map &f }.describe(self.to_s)
|
117
|
+
end
|
118
|
+
|
119
|
+
alias_method :mapr, :map_in_parse_result
|
120
|
+
|
121
|
+
# monadic bind (or flat map) for a parser
|
122
|
+
# f should take the result of the current parser and return a the parser that will consume
|
123
|
+
# the next input. Used for sequencing
|
124
|
+
def flat_map(&f)
|
125
|
+
parser do |input|
|
126
|
+
self[input].flat_map_with_next &f
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
# Returns a parser that, if self is successful will dicard its result
|
131
|
+
# Used in combination with sequencing to achieve lookahead / lookbehind
|
132
|
+
def discard
|
133
|
+
parser do |input|
|
134
|
+
result = self[input]
|
135
|
+
|
136
|
+
if Success === result
|
137
|
+
Success(Empty(), result.remaining)
|
138
|
+
else
|
139
|
+
result
|
140
|
+
end
|
141
|
+
end.describe(self.to_s)
|
142
|
+
end
|
143
|
+
|
144
|
+
# sequening: returns a parser that succeeds if self succeeds,
|
145
|
+
# follwing by the otheer parser on the remaining input
|
146
|
+
# returns a Tuple of both results
|
147
|
+
def >(other)
|
148
|
+
self.flat_map do |result1|
|
149
|
+
other.map do |result2|
|
150
|
+
result1 + result2
|
151
|
+
end
|
152
|
+
end.describe("#{self.to_s} #{other.to_s}")
|
153
|
+
end
|
154
|
+
|
155
|
+
# sequencing: same as '>' but returns only the result of the +other+
|
156
|
+
# parser, discarding the result of the first
|
157
|
+
def >=(other)
|
158
|
+
(self.discard > other)
|
159
|
+
end
|
160
|
+
|
161
|
+
# sequencing: same as '>' but returns only the result of +self+, disregarding
|
162
|
+
# the result of the +other+
|
163
|
+
def <=(other)
|
164
|
+
(self > other.discard)
|
165
|
+
end
|
166
|
+
|
167
|
+
# alternation: try this parser and if it fails (but not with an Error)
|
168
|
+
# try the +other+ parser
|
169
|
+
def |(other)
|
170
|
+
parser do |input|
|
171
|
+
result1 = self[input]
|
172
|
+
|
173
|
+
case result1
|
174
|
+
when Success
|
175
|
+
result1
|
176
|
+
when Failure
|
177
|
+
result2 = other[input]
|
178
|
+
|
179
|
+
# return the failure that happened earlier in the stream
|
180
|
+
if Failure == result2 && result1.remaining.count > result2.remaining.count
|
181
|
+
result1
|
182
|
+
else
|
183
|
+
result2
|
184
|
+
end
|
185
|
+
when Error
|
186
|
+
result1
|
187
|
+
end
|
188
|
+
end.describe("#{self.to_s} | #{other.to_s}")
|
189
|
+
end
|
190
|
+
|
191
|
+
# makes this parser opitonal
|
192
|
+
def optional
|
193
|
+
self.map do |parse_result|
|
194
|
+
case parse_result
|
195
|
+
when Success
|
196
|
+
parse_result
|
197
|
+
else
|
198
|
+
Success(Empty(), parse_result.remaining)
|
199
|
+
end
|
200
|
+
end.describe("[#{self.to_s}]")
|
201
|
+
end
|
202
|
+
|
203
|
+
# creates a parser that will reject a whatever this parser succeeds in parsing
|
204
|
+
# if this parser fails, the 'rejected' version will succeed with Empty() nad
|
205
|
+
# will not consume any input
|
206
|
+
def reject
|
207
|
+
parser do |input|
|
208
|
+
result = self[input]
|
209
|
+
|
210
|
+
if result.success?
|
211
|
+
Failure("Unexpected #{self.to_s}", result.remaining)
|
212
|
+
else
|
213
|
+
Success(Empty(), input)
|
214
|
+
end
|
215
|
+
end.describe("!#{self.to_s}")
|
216
|
+
end
|
217
|
+
|
218
|
+
# returns a parser that repeatedly parses
|
219
|
+
# with +self+ until it fails
|
220
|
+
def repeats(kind = :*, till: self.reject | eos("eos"))
|
221
|
+
|
222
|
+
case kind
|
223
|
+
when :* then (rep1(till: till) | success(Empty())).describe("[#{self} ...]")
|
224
|
+
when :+ then rep1(till: till).describe( "#{self} [#{self} ...]")
|
225
|
+
else raise "Unknown repetition kind for #{self}"
|
226
|
+
end
|
227
|
+
end
|
228
|
+
|
229
|
+
|
230
|
+
def rep1(till: self.reject)
|
231
|
+
parser do |input|
|
232
|
+
results = self[input]
|
233
|
+
remaining = results.remaining
|
234
|
+
|
235
|
+
if results.success?
|
236
|
+
while results.success? && !till[remaining].success?
|
237
|
+
results += self[remaining]
|
238
|
+
remaining = results.remaining
|
239
|
+
end
|
240
|
+
end
|
241
|
+
|
242
|
+
till_result = till[remaining]
|
243
|
+
if till_result.success?
|
244
|
+
results
|
245
|
+
else
|
246
|
+
till_result
|
247
|
+
end
|
248
|
+
end
|
249
|
+
end
|
250
|
+
|
251
|
+
private :rep1
|
252
|
+
|
253
|
+
# attach a description to the parser
|
254
|
+
def describe(str)
|
255
|
+
self.define_singleton_method(:to_s) do
|
256
|
+
str
|
257
|
+
end
|
258
|
+
|
259
|
+
self
|
260
|
+
end
|
261
|
+
|
262
|
+
# converts the failure message
|
263
|
+
def with_failure_message(&failure_converter)
|
264
|
+
parser do |input|
|
265
|
+
result = self[input]
|
266
|
+
|
267
|
+
case result
|
268
|
+
when Success
|
269
|
+
result
|
270
|
+
when Failure
|
271
|
+
Failure(failure_converter[result.message, input], result.remaining)
|
272
|
+
end
|
273
|
+
end.describe self.to_s
|
274
|
+
end
|
275
|
+
end
|
276
|
+
end
|
277
|
+
end
|
278
|
+
end
|