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.
@@ -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