haskell_match 0.1.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.
Files changed (50) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +98 -0
  3. data/LICENSE-APACHE +202 -0
  4. data/LICENSE-MIT +21 -0
  5. data/README.md +1484 -0
  6. data/ext/haskell_match/Cargo.lock +33 -0
  7. data/ext/haskell_match/Cargo.toml +22 -0
  8. data/ext/haskell_match/extconf.rb +41 -0
  9. data/ext/haskell_match/src/core/ast.rs +190 -0
  10. data/ext/haskell_match/src/core/error.rs +52 -0
  11. data/ext/haskell_match/src/core/exhaust.rs +699 -0
  12. data/ext/haskell_match/src/core/hs/ast.rs +256 -0
  13. data/ext/haskell_match/src/core/hs/json.rs +225 -0
  14. data/ext/haskell_match/src/core/hs/layout.rs +346 -0
  15. data/ext/haskell_match/src/core/hs/lexer.rs +688 -0
  16. data/ext/haskell_match/src/core/hs/mod.rs +14 -0
  17. data/ext/haskell_match/src/core/hs/parser.rs +1945 -0
  18. data/ext/haskell_match/src/core/lexer.rs +590 -0
  19. data/ext/haskell_match/src/core/mod.rs +19 -0
  20. data/ext/haskell_match/src/core/parser.rs +1116 -0
  21. data/ext/haskell_match/src/core/pretty.rs +373 -0
  22. data/ext/haskell_match/src/core/resolve.rs +336 -0
  23. data/ext/haskell_match/src/core/tree.rs +921 -0
  24. data/ext/haskell_match/src/core/typecheck.rs +226 -0
  25. data/ext/haskell_match/src/core/types.rs +404 -0
  26. data/ext/haskell_match/src/lib.rs +19 -0
  27. data/ext/haskell_match/src/ruby/mod.rs +1195 -0
  28. data/ext/haskell_match/src/ruby/runtime.rs +1045 -0
  29. data/lib/haskell_match/binding_plan.rb +84 -0
  30. data/lib/haskell_match/case_of.rb +71 -0
  31. data/lib/haskell_match/clauses.rb +354 -0
  32. data/lib/haskell_match/data.rb +417 -0
  33. data/lib/haskell_match/deep_call.rb +98 -0
  34. data/lib/haskell_match/deriving.rb +130 -0
  35. data/lib/haskell_match/dsl.rb +71 -0
  36. data/lib/haskell_match/errors.rb +85 -0
  37. data/lib/haskell_match/field_types.rb +140 -0
  38. data/lib/haskell_match/function.rb +240 -0
  39. data/lib/haskell_match/haskell/compiler.rb +961 -0
  40. data/lib/haskell_match/haskell.rb +326 -0
  41. data/lib/haskell_match/inspect.rb +45 -0
  42. data/lib/haskell_match/lazy_list.rb +210 -0
  43. data/lib/haskell_match/native_loader.rb +64 -0
  44. data/lib/haskell_match/pattern.rb +75 -0
  45. data/lib/haskell_match/pattern_ast.rb +394 -0
  46. data/lib/haskell_match/prelude.rb +448 -0
  47. data/lib/haskell_match/scope.rb +44 -0
  48. data/lib/haskell_match/version.rb +5 -0
  49. data/lib/haskell_match.rb +41 -0
  50. metadata +124 -0
@@ -0,0 +1,84 @@
1
+ # frozen_string_literal: true
2
+
3
+ module HaskellMatch
4
+ # Works out how a clause body (or guard) wants its bound variables and
5
+ # produces a callable taking them positionally, in pattern order, as the
6
+ # native matcher delivers them.
7
+ #
8
+ # * No parameters, or a splat: the values are passed positionally.
9
+ # * Positional parameters: each must name a bound variable; values are
10
+ # passed by name, so `on("(x:xs)") { |xs| ... }` receives `xs`.
11
+ # * Keyword parameters: passed by name as keywords.
12
+ module BindingPlan
13
+ module_function
14
+
15
+ # Returns a callable that accepts the bound values positionally.
16
+ def adapt(callable, names, clause_index, role, function_name)
17
+ params = callable.parameters
18
+ if params.empty?
19
+ # a lambda taking nothing must not be handed the bound values
20
+ return callable unless callable.lambda? && !names.empty?
21
+
22
+ return ->(*) { callable.call }
23
+ end
24
+
25
+ kinds = params.map(&:first)
26
+ if kinds.include?(:keyreq) || kinds.include?(:key) || kinds.include?(:keyrest)
27
+ return adapt_keywords(callable, params, names, clause_index, role, function_name)
28
+ end
29
+ return callable if kinds.include?(:rest)
30
+ if kinds.include?(:block)
31
+ params = params.reject { |k, _| k == :block }
32
+ return callable if params.empty?
33
+ end
34
+
35
+ wanted = params.map { |_, n| n }
36
+ if wanted.any?(&:nil?)
37
+ # e.g. `|(a, b)|` destructuring parameters: pass positionally
38
+ return callable
39
+ end
40
+ unknown = wanted.reject { |n| names.include?(n.to_s) }
41
+ unless unknown.empty?
42
+ raise DefinitionError,
43
+ "#{role} of clause #{clause_index + 1} of '#{function_name}' names " \
44
+ "#{unknown.map(&:inspect).join(', ')} but the pattern binds " \
45
+ "#{names.empty? ? 'no variables' : names.join(', ')}"
46
+ end
47
+ indexes = wanted.map { |n| names.index(n.to_s) }
48
+ return callable if indexes == (0...indexes.size).to_a && indexes.size == names.size
49
+ return callable if indexes == (0...indexes.size).to_a && !callable.lambda?
50
+
51
+ lambda do |*values|
52
+ callable.call(*indexes.map { |i| values[i] })
53
+ end
54
+ end
55
+
56
+ def adapt_keywords(callable, params, names, clause_index, role, function_name)
57
+ positional = params.select { |k, _| %i[req opt].include?(k) }
58
+ unless positional.empty?
59
+ raise DefinitionError,
60
+ "#{role} of clause #{clause_index + 1} of '#{function_name}' mixes positional and " \
61
+ "keyword parameters; use one style"
62
+ end
63
+ keys = params.select { |k, _| %i[keyreq key].include?(k) }.map { |_, n| n }
64
+ unknown = keys.reject { |n| names.include?(n.to_s) }
65
+ unless unknown.empty?
66
+ raise DefinitionError,
67
+ "#{role} of clause #{clause_index + 1} of '#{function_name}' names keyword(s) " \
68
+ "#{unknown.map(&:inspect).join(', ')} but the pattern binds " \
69
+ "#{names.empty? ? 'no variables' : names.join(', ')}"
70
+ end
71
+ symbols = names.map(&:to_sym)
72
+ if params.any? { |k, _| k == :keyrest }
73
+ lambda do |*values|
74
+ callable.call(**symbols.zip(values).to_h)
75
+ end
76
+ else
77
+ picks = keys.map { |k| [k, names.index(k.to_s)] }
78
+ lambda do |*values|
79
+ callable.call(**picks.to_h { |k, i| [k, values[i]] })
80
+ end
81
+ end
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,71 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "monitor"
4
+
5
+ module HaskellMatch
6
+ # `case ... of` expressions.
7
+ #
8
+ # HaskellMatch.case_of(value) do
9
+ # on("Just x") { |x| x }
10
+ # on("Nothing") { 0 }
11
+ # end
12
+ #
13
+ # The decision tree for a given set of patterns is compiled once and cached,
14
+ # so repeated evaluation only pays for collecting the clause blocks.
15
+ module CaseOf
16
+ CACHE = {}
17
+ LOCK = Monitor.new
18
+ MAX_CACHE = 4096
19
+
20
+ module_function
21
+
22
+ def evaluate(values, exhaustive:, overlapping:, scope: Native::GLOBAL_SCOPE, &definition)
23
+ raise ArgumentError, "case_of needs a block with on(...) clauses" unless definition
24
+ raise ArgumentError, "case_of needs at least one value" if values.empty?
25
+
26
+ builder = ClauseBuilder.collect(definition)
27
+ clauses = builder.clauses
28
+ key = [scope, cache_key(clauses, exhaustive, overlapping)]
29
+ entry = LOCK.synchronize { CACHE[key] }
30
+ unless entry
31
+ name = "case expression at #{definition.source_location&.join(':')}"
32
+ matcher, _bodies, _guards = Compiler.compile(name, clauses, exhaustive: exhaustive, overlapping: overlapping,
33
+ kind: "case expression", scope: scope,
34
+ resolver: builder.constant_resolver)
35
+ entry = [matcher, matcher.names]
36
+ LOCK.synchronize do
37
+ CACHE.clear if CACHE.size >= MAX_CACHE
38
+ CACHE[key] = entry
39
+ end
40
+ end
41
+ matcher, names = entry
42
+ bodies = clauses.each_with_index.map { |c, i| BindingPlan.adapt(c.body, names[i], i, "body", "case") }
43
+ guards = clauses.each_with_index.map do |c, i|
44
+ next nil if c.guard.nil? || c.guard.equal?(ClauseBuilder::OTHERWISE)
45
+
46
+ BindingPlan.adapt(c.guard, names[i], i, "guard", "case")
47
+ end
48
+ guards = nil if guards.all?(&:nil?)
49
+ matcher.run(values, bodies, guards)
50
+ end
51
+
52
+ def cache_key(clauses, exhaustive, overlapping)
53
+ [
54
+ clauses.map { |c| [c.patterns, !c.guard.nil? && !c.guard.equal?(ClauseBuilder::OTHERWISE)] },
55
+ exhaustive, overlapping
56
+ ]
57
+ end
58
+
59
+ def clear_cache
60
+ LOCK.synchronize { CACHE.clear }
61
+ end
62
+ end
63
+
64
+ class << self
65
+ # Evaluate a `case ... of` expression over one or more values.
66
+ def case_of(*values, exhaustive: self.exhaustive, overlapping: self.overlapping, scope: Native::GLOBAL_SCOPE,
67
+ &definition)
68
+ CaseOf.evaluate(values, exhaustive: exhaustive, overlapping: overlapping, scope: scope, &definition)
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,354 @@
1
+ # frozen_string_literal: true
2
+
3
+ module HaskellMatch
4
+ # One `on(...)` clause: argument patterns, optional guard, body.
5
+ Clause = Struct.new(:patterns, :guard, :body, :location) do
6
+ def to_s
7
+ patterns.join(" ")
8
+ end
9
+ end
10
+
11
+ # Collects clauses from a definition block.
12
+ #
13
+ # on("[]") { 0 }
14
+ # on("(_:xs)") { |xs| 1 + length.(xs) }
15
+ # on("n", guard: ->(n) { n > 0 }) { :positive }
16
+ #
17
+ # The definition block runs with `self` set to the builder; method calls the
18
+ # builder does not understand are forwarded to the object that owns the
19
+ # block, so helper methods of the surrounding class or test remain
20
+ # callable. Instance variables of that object are not visible; take the
21
+ # builder as a block parameter (`fn { |m| m.on(...) { @x } }`) when needed.
22
+ class ClauseBuilder
23
+ include PatternAST::BuilderMethods
24
+
25
+ attr_reader :clauses
26
+
27
+ def initialize(owner = nil, options = {})
28
+ @clauses = []
29
+ @owner = owner
30
+ @options = options
31
+ @helpers = {}
32
+ end
33
+
34
+ # A local helper function, like a Haskell `where` binding: defined with
35
+ # its own clauses and reachable by name from every clause body of the
36
+ # enclosing function (and from the other helpers). It is a full
37
+ # {Function}: checked for exhaustiveness, with `tail` and `defer`, and
38
+ # compiled with the enclosing function's options unless overridden.
39
+ #
40
+ # sum_to = HaskellMatch.fn(:sum_to) do
41
+ # on(n) { |n| go.(n, 0) }
42
+ # where :go do
43
+ # on(0, acc) { |acc| acc }
44
+ # on(k, acc) { |k, acc| go.tail(k - 1, acc + k) }
45
+ # end
46
+ # end
47
+ def where(name, **options, &definition)
48
+ raise DefinitionError, "where needs a block of on(...) clauses" unless definition
49
+
50
+ name = name.to_s
51
+ raise DefinitionError, "'#{name}' is not a valid helper name" unless name.match?(NAME_PATTERN)
52
+ raise DefinitionError, "helper '#{name}' is defined twice" if @helpers.key?(name)
53
+
54
+ helper = HaskellMatch.fn(name, **@options.merge(options), &definition)
55
+ @helpers[name] = helper
56
+ define_singleton_method(name) { helper }
57
+ helper
58
+ end
59
+
60
+ # Helper functions defined with {#where}, by name.
61
+ def helpers
62
+ @helpers.dup
63
+ end
64
+
65
+ # Resolve a constant the way code at the definition block would see it:
66
+ # through the receiver (a module, or an object's class) and so through
67
+ # its ancestors and `Object`.
68
+ def constant_resolver
69
+ owner = @owner
70
+ lambda do |name|
71
+ home = owner.is_a?(Module) ? owner : owner.class
72
+ home.const_defined?(name) ? home.const_get(name) : nil
73
+ rescue NameError
74
+ nil
75
+ end
76
+ end
77
+
78
+ # Add a clause: one pattern per argument, each either a Haskell pattern
79
+ # string or a pattern written in place (see {PatternAST}).
80
+ def on(*patterns, guard: nil, where: nil, location: nil, &body)
81
+ guard ||= where
82
+ raise DefinitionError, "a clause needs at least one pattern" if patterns.empty?
83
+ raise DefinitionError, "a clause needs a body block" unless body
84
+ if guard && !guard.respond_to?(:call)
85
+ raise DefinitionError, "guard must be callable (a Proc or lambda)"
86
+ end
87
+
88
+ texts = patterns.map { |p| p.is_a?(String) ? p.dup.freeze : PatternAST.render(p).freeze }
89
+ @clauses << Clause.new(texts.freeze, guard, body, location || body.source_location)
90
+ self
91
+ end
92
+ alias clause on
93
+
94
+ # Haskell's `otherwise`: a guard that always passes.
95
+ def otherwise
96
+ OTHERWISE
97
+ end
98
+
99
+ OTHERWISE = ->(*) { true }.freeze
100
+
101
+ # The function being defined, for recursion from inside its own clause
102
+ # bodies (also reachable under the function's own name).
103
+ def recur
104
+ @function or raise DefinitionError, "the function is not defined yet"
105
+ end
106
+ alias this recur
107
+
108
+ def method_missing(name, *args, **kwargs, &blk)
109
+ if @owner && @owner.respond_to?(name, true)
110
+ @owner.__send__(name, *args, **kwargs, &blk)
111
+ else
112
+ super
113
+ end
114
+ end
115
+
116
+ def respond_to_missing?(name, include_private = false)
117
+ (@owner && @owner.respond_to?(name, include_private)) || super
118
+ end
119
+
120
+ def pattern_owner_responds?(name)
121
+ !@owner.nil? && @owner.respond_to?(name, true)
122
+ end
123
+
124
+ # Run a definition block: instance_exec'd on the builder when it takes no
125
+ # parameters, otherwise called with the builder as its argument. Returns
126
+ # the builder; call {#define_function} once the function exists so clause
127
+ # bodies can recurse through `recur` or the function's name.
128
+ def self.collect(definition, ractor: false, options: {})
129
+ builder = ractor ? RactorHost.new_builder(options) : new(definition.binding.receiver, options)
130
+ if definition.arity.zero?
131
+ builder.instance_exec(&definition)
132
+ else
133
+ definition.call(builder)
134
+ end
135
+ builder
136
+ end
137
+
138
+ NAME_PATTERN = /\A[a-z_][A-Za-z0-9_]*[?!]?\z/
139
+
140
+ # Make the finished function reachable from its clause bodies as `recur`
141
+ # and, when the name is a valid method name, as `name`.
142
+ def define_function(function, name)
143
+ @function = function
144
+ return unless name.is_a?(String) && name.match?(NAME_PATTERN) && !real_method?(name)
145
+
146
+ define_singleton_method(name) { function }
147
+ end
148
+
149
+ # A method actually defined on the builder (not one `method_missing`
150
+ # would synthesise as a pattern variable).
151
+ def real_method?(name)
152
+ singleton_class.method_defined?(name) || singleton_class.private_method_defined?(name)
153
+ end
154
+
155
+ # Clause-builder behaviour for Ractor-shareable functions. A Module is
156
+ # used as the builder (and so as `self` of the clause bodies) because
157
+ # modules are always shareable while remaining open for a constant that
158
+ # points back at the finished function.
159
+ module RactorHost
160
+ include PatternAST::BuilderMethods
161
+
162
+ def self.new_builder(options = {})
163
+ host = Module.new
164
+ host.extend(RactorHost)
165
+ host.instance_variable_set(:@clauses, [])
166
+ host.instance_variable_set(:@options, options)
167
+ host.instance_variable_set(:@helpers, {})
168
+ host
169
+ end
170
+
171
+ def clauses
172
+ @clauses
173
+ end
174
+
175
+ # A `where` helper (see {ClauseBuilder#where}); in Ractor mode helpers
176
+ # are themselves shareable functions and cannot call back into the
177
+ # enclosing function by name.
178
+ def where(name, **options, &definition)
179
+ raise DefinitionError, "where needs a block of on(...) clauses" unless definition
180
+
181
+ name = name.to_s
182
+ raise DefinitionError, "'#{name}' is not a valid helper name" unless name.match?(NAME_PATTERN)
183
+ raise DefinitionError, "helper '#{name}' is defined twice" if @helpers.key?(name)
184
+
185
+ helper = HaskellMatch.fn(name, **@options.merge(options), ractor: true, &definition)
186
+ @helpers[name] = helper
187
+ define_singleton_method(name, &Ractor.make_shareable(-> { helper }))
188
+ helper
189
+ end
190
+
191
+ def helpers
192
+ @helpers.dup
193
+ end
194
+
195
+ def on(*patterns, guard: nil, where: nil, location: nil, &body)
196
+ guard ||= where
197
+ raise DefinitionError, "a clause needs at least one pattern" if patterns.empty?
198
+ raise DefinitionError, "a clause needs a body block" unless body
199
+ if guard && !guard.respond_to?(:call)
200
+ raise DefinitionError, "guard must be callable (a Proc or lambda)"
201
+ end
202
+
203
+ texts = patterns.map { |p| p.is_a?(String) ? p.dup.freeze : PatternAST.render(p).freeze }
204
+ @clauses << Clause.new(texts.freeze, guard, body, location || body.source_location)
205
+ self
206
+ end
207
+ alias clause on
208
+
209
+ def otherwise
210
+ OTHERWISE
211
+ end
212
+
213
+ # Constant lookup is the Ractor-safe way back to the function.
214
+ def recur
215
+ const_get(:FUNCTION)
216
+ end
217
+ alias this recur
218
+
219
+ def define_function(function, name)
220
+ const_set(:FUNCTION, function)
221
+ return if !name.is_a?(String) || !name.match?(NAME_PATTERN)
222
+ return if singleton_class.method_defined?(name) || singleton_class.private_method_defined?(name)
223
+
224
+ host = self
225
+ define_singleton_method(name, &Ractor.make_shareable(-> { host.const_get(:FUNCTION) }))
226
+ end
227
+ end
228
+ end
229
+
230
+ # Compiles clauses into a native matcher and enforces exhaustiveness and
231
+ # redundancy policy. Shared by {Function}, {CaseOf} and {Pattern}.
232
+ module Compiler
233
+ WITNESS_LIMIT = 20
234
+
235
+ POLICIES = %i[error warn ignore].freeze
236
+
237
+ module_function
238
+
239
+ def policy(value, option)
240
+ case value
241
+ when true then :error
242
+ when false, nil then :ignore
243
+ when :error, :warn, :ignore then value
244
+ else
245
+ raise ArgumentError, "#{option}: must be true, false, :error, :warn or :ignore (got #{value.inspect})"
246
+ end
247
+ end
248
+
249
+ # Construct a native matcher, registering top-level Ruby `Data`/`Struct`
250
+ # classes named by unknown constructors on the way and adding a caret to
251
+ # syntax errors.
252
+ def build_matcher(klass, name, patterns, guard_flags, limit, scope, resolver: nil)
253
+ tried = []
254
+ begin
255
+ klass.new(name, patterns, guard_flags, limit, scope)
256
+ rescue UnknownConstructorError => e
257
+ con = e.message[/data constructor '([^']+)'/, 1]
258
+ raise if con.nil? || tried.include?(con) || !autoregister(con, scope, resolver)
259
+
260
+ tried << con
261
+ retry
262
+ rescue PatternSyntaxError => e
263
+ raise e.exception(PatternSyntaxError.with_caret(e.message)), cause: nil
264
+ end
265
+ end
266
+
267
+ # A pattern named a constructor nobody declared: if a Ruby `Data` or
268
+ # `Struct` class of that name is visible (through `resolver`, which sees
269
+ # what the definition block sees, or at top level), it becomes a type of
270
+ # its own (`HaskellMatch.sealed`) so plain Ruby value classes match
271
+ # without a declaration. Returns whether a type was registered.
272
+ def autoregister(name, scope, resolver = nil)
273
+ return false if HaskellMatch.constructor(name, scope)
274
+
275
+ klass = resolver&.call(name)
276
+ klass = Object.const_get(name) if klass.nil? && Object.const_defined?(name)
277
+ return false unless klass.is_a?(Class) && (klass < Data || klass < Struct)
278
+ return false if klass.singleton_class.include?(Constructor::SealedClassMethods) || klass.include?(Constructor)
279
+
280
+ HaskellMatch.sealed(name, klass, under: nil, scope: scope)
281
+ true
282
+ rescue NameError, CompileError
283
+ false
284
+ end
285
+
286
+ # Returns [matcher, bodies, guards] where bodies/guards are arrays of
287
+ # callables taking the bound values positionally.
288
+ def compile(name, clauses, exhaustive:, overlapping:, kind: "equation", klass: Native::Matcher,
289
+ scope: Native::GLOBAL_SCOPE, resolver: nil)
290
+ raise DefinitionError, "'#{name}' has no clauses" if clauses.empty?
291
+
292
+ exhaustive = policy(exhaustive, :exhaustive)
293
+ overlapping = policy(overlapping, :overlapping)
294
+ matcher = build_matcher(
295
+ klass,
296
+ name.to_s,
297
+ clauses.map(&:patterns),
298
+ clauses.map { |c| !c.guard.nil? && !c.guard.equal?(ClauseBuilder::OTHERWISE) },
299
+ WITNESS_LIMIT,
300
+ scope,
301
+ resolver: resolver
302
+ )
303
+ report(name, kind, clauses, matcher, exhaustive, overlapping)
304
+
305
+ names = matcher.names
306
+ bodies = clauses.each_with_index.map do |c, i|
307
+ BindingPlan.adapt(c.body, names[i], i, "body", name)
308
+ end
309
+ guards = clauses.each_with_index.map do |c, i|
310
+ next nil if c.guard.nil? || c.guard.equal?(ClauseBuilder::OTHERWISE)
311
+
312
+ BindingPlan.adapt(c.guard, names[i], i, "guard", name)
313
+ end
314
+ guards = nil if guards.all?(&:nil?)
315
+ [matcher, bodies, guards]
316
+ end
317
+
318
+ def report(name, kind, clauses, matcher, exhaustive, overlapping)
319
+ redundant = matcher.instance_variable_get(:@redundant)
320
+ if !redundant.empty? && overlapping != :ignore
321
+ lines = redundant.map do |i|
322
+ c = clauses[i]
323
+ loc = c.location ? " (#{c.location.join(':')})" : ""
324
+ " #{name} #{c} = ...#{loc}"
325
+ end
326
+ message = "Pattern match#{redundant.size > 1 ? 'es are' : ' is'} redundant\n" \
327
+ "In #{article(kind)} #{kind} for '#{name}':\n#{lines.join("\n")}"
328
+ if overlapping == :error
329
+ raise RedundantClauseError.new(message, redundant)
330
+ else
331
+ warn "haskell_match: #{message}"
332
+ end
333
+ end
334
+
335
+ missing = matcher.instance_variable_get(:@missing)
336
+ if !missing.empty? && exhaustive != :ignore
337
+ shown = missing.map { |m| " #{m.empty? ? '(no clause can match)' : m}" }
338
+ shown << " ..." if matcher.instance_variable_get(:@missing_truncated)
339
+ message = "Pattern match(es) are non-exhaustive\n" \
340
+ "In #{article(kind)} #{kind} for '#{name}':\n" \
341
+ " Patterns not matched:\n#{shown.map { |s| ' ' + s }.join("\n")}"
342
+ if exhaustive == :error
343
+ raise NonExhaustiveError.new(message, missing)
344
+ else
345
+ warn "haskell_match: #{message}"
346
+ end
347
+ end
348
+ end
349
+
350
+ def article(word)
351
+ word.match?(/\A[aeiou]/i) ? "an" : "a"
352
+ end
353
+ end
354
+ end