drudge 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|