drudge 0.4.0

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