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,448 @@
1
+ # frozen_string_literal: true
2
+
3
+ module HaskellMatch
4
+ # The functions Haskell code compiled by haskell_match can call without
5
+ # defining them. Each is a Ruby module function of fixed arity; `curried`
6
+ # gives the curried Proc used when a function is passed as a value.
7
+ #
8
+ # Lists are Ruby Arrays, Strings (lists of characters) or {LazyList}s;
9
+ # functions that build lists stay lazy when their input is lazy.
10
+ module Prelude
11
+ # Raised by `error` and `undefined`.
12
+ class HaskellError < Error; end
13
+
14
+ ARITY = {} # name => arity
15
+ @curried = {}
16
+
17
+ class << self
18
+ def defn(name, arity, &impl)
19
+ ARITY[name] = arity
20
+ define_singleton_method(name, &impl)
21
+ end
22
+
23
+ def arity(name)
24
+ ARITY[name]
25
+ end
26
+
27
+ def known?(name)
28
+ ARITY.key?(name)
29
+ end
30
+
31
+ # The function as a curried Proc (memoised).
32
+ def curried(name)
33
+ @curried[name] ||= begin
34
+ m = method(name)
35
+ case ARITY.fetch(name)
36
+ when 0 then m.to_proc
37
+ when 1 then m.to_proc
38
+ else m.to_proc.curry
39
+ end
40
+ end
41
+ end
42
+
43
+ # Curry any callable of known arity.
44
+ def curry(callable, arity)
45
+ return callable if arity <= 1
46
+
47
+ callable.to_proc.curry(arity)
48
+ end
49
+
50
+ # ---- list plumbing ---------------------------------------------------
51
+
52
+ def lazy?(xs)
53
+ xs.is_a?(LazyList) || xs.is_a?(Enumerator)
54
+ end
55
+
56
+ # An Enumerator::Lazy over any list value.
57
+ def lazy_enum(xs)
58
+ case xs
59
+ when Array then xs.lazy
60
+ when String then xs.each_char.lazy
61
+ when LazyList then xs.each.lazy
62
+ when Enumerator then xs.lazy
63
+ else raise TypeError, "#{xs.inspect} is not a list"
64
+ end
65
+ end
66
+
67
+ # Materialise an Enumerable into the kind of list `like` was.
68
+ def relist(enum, like)
69
+ return LazyList.from(enum) if lazy?(like)
70
+
71
+ out = enum.to_a
72
+ out = out.join if like.is_a?(String) && out.all? { |c| c.is_a?(String) && c.length == 1 }
73
+ out
74
+ end
75
+
76
+ # Prelude values go through the registry so they stay compatible with a
77
+ # program that redeclares `Maybe` or `Ordering` itself.
78
+ def just(x)
79
+ HaskellMatch.constructor("Just").new(x)
80
+ end
81
+
82
+ def nothing
83
+ HaskellMatch.constructor("Nothing")
84
+ end
85
+
86
+ def just?(m)
87
+ m.is_a?(Constructor) && m.class.constructor_name == "Just"
88
+ end
89
+
90
+ # ---- operators used by generated code --------------------------------
91
+
92
+ def cons(x, xs)
93
+ case xs
94
+ when LazyList, Enumerator then LazyList.cons(x, xs)
95
+ when String then x.is_a?(String) ? x + xs : [x] + xs.chars
96
+ when Array then [x] + xs
97
+ else raise TypeError, "cannot cons onto #{xs.inspect}"
98
+ end
99
+ end
100
+
101
+ def append(xs, ys)
102
+ if lazy?(xs) || lazy?(ys)
103
+ return LazyList.from(Enumerator.new { |y| lazy_enum(xs).each { |e| y << e }; lazy_enum(ys).each { |e| y << e } })
104
+ end
105
+ return xs + ys if xs.class == ys.class
106
+
107
+ (xs.is_a?(String) ? xs.chars : xs) + (ys.is_a?(String) ? ys.chars : ys)
108
+ end
109
+
110
+ def compose(f, g)
111
+ ->(x) { f.(g.(x)) }
112
+ end
113
+
114
+ def fdiv(a, b)
115
+ a.is_a?(Integer) && b.is_a?(Integer) ? a.fdiv(b) : a / b
116
+ end
117
+
118
+ def powf(a, b)
119
+ a.to_f**b
120
+ end
121
+
122
+ def index(xs, n)
123
+ raise HaskellError, "Prelude.!!: negative index" if n.negative?
124
+
125
+ v = case xs
126
+ when LazyList then xs.take(n + 1)[n]
127
+ when Enumerator then xs.lazy.first(n + 1)[n]
128
+ else xs[n]
129
+ end
130
+ raise HaskellError, "Prelude.!!: index too large" if v.nil? && !(xs.respond_to?(:size) && n < xs.size)
131
+
132
+ v
133
+ end
134
+
135
+ # [from..], [from..to], [from, then ..], [from, then .. to]
136
+ def range(from, step_to = nil, to = nil)
137
+ # `[Red ..]`, `['a'..'z']`: enumerations and characters go by index
138
+ return enum_range(from, step_to, to) if from.respond_to?(:from_enum) && from.respond_to?(:data_type)
139
+ return char_range(from, step_to, to) if from.is_a?(String)
140
+
141
+ step = step_to.nil? ? 1 : step_to - from
142
+ if to.nil?
143
+ return LazyList.from(Enumerator.produce(from) { |x| x + step })
144
+ end
145
+ return [] if step.zero? && from > to
146
+
147
+ out = []
148
+ x = from
149
+ if step.positive?
150
+ while x <= to
151
+ out << x
152
+ x += step
153
+ end
154
+ elsif step.negative?
155
+ while x >= to
156
+ out << x
157
+ x += step
158
+ end
159
+ else
160
+ raise HaskellError, "enumFromThenTo: zero step"
161
+ end
162
+ out
163
+ end
164
+
165
+ # Ranges over a `deriving Enum` type, as `enumFrom`, `enumFromThen`,
166
+ # `enumFromTo` and `enumFromThenTo` would produce.
167
+ def enum_range(from, step_to, to)
168
+ type = from.data_type
169
+ if to.nil?
170
+ step_to.nil? ? type.enum_from(from) : type.enum_from_then(from, step_to)
171
+ else
172
+ step_to.nil? ? type.enum_from_to(from, to) : type.enum_from_then_to(from, step_to, to)
173
+ end
174
+ end
175
+
176
+ # Ranges over characters: `['a'..'z']`, `['a', 'c'..]`.
177
+ def char_range(from, step_to, to)
178
+ step = step_to.nil? ? 1 : step_to.ord - from.ord
179
+ if to.nil?
180
+ return LazyList.from(Enumerator.produce(from.ord) { |c| c + step }.lazy.map(&:chr))
181
+ end
182
+
183
+ out = range(from.ord, step_to&.ord, to.ord)
184
+ out.map(&:chr).join
185
+ end
186
+
187
+ def op(name)
188
+ @ops ||= {
189
+ "+" => ->(a) { ->(b) { a + b } }, "-" => ->(a) { ->(b) { a - b } }, "*" => ->(a) { ->(b) { a * b } },
190
+ "/" => ->(a) { ->(b) { fdiv(a, b) } }, "^" => ->(a) { ->(b) { a**b } }, "**" => ->(a) { ->(b) { powf(a, b) } },
191
+ "==" => ->(a) { ->(b) { a == b } }, "/=" => ->(a) { ->(b) { a != b } },
192
+ "<" => ->(a) { ->(b) { a < b } }, "<=" => ->(a) { ->(b) { a <= b } },
193
+ ">" => ->(a) { ->(b) { a > b } }, ">=" => ->(a) { ->(b) { a >= b } },
194
+ "&&" => ->(a) { ->(b) { a && b } }, "||" => ->(a) { ->(b) { a || b } },
195
+ "++" => ->(a) { ->(b) { append(a, b) } }, ":" => ->(a) { ->(b) { cons(a, b) } },
196
+ "." => ->(f) { ->(g) { compose(f, g) } }, "$" => ->(f) { ->(x) { f.(x) } },
197
+ "!!" => ->(a) { ->(b) { index(a, b) } }
198
+ }.freeze
199
+ @ops.fetch(name) do
200
+ raise HaskellError, "operator #{name} is not supported" unless known?(name.to_sym)
201
+
202
+ curried(name.to_sym)
203
+ end
204
+ end
205
+
206
+ # ---- list comprehensions ----------------------------------------------
207
+
208
+ # Each comprehension evaluates its generators through `gen`, which
209
+ # records whether any source was lazy; `finish` then returns a LazyList
210
+ # or an Array accordingly.
211
+ def comp_begin
212
+ (@comp_lazy ||= []).push(false)
213
+ end
214
+
215
+ def gen(xs)
216
+ @comp_lazy[-1] = true if lazy?(xs)
217
+ lazy_enum(xs)
218
+ end
219
+
220
+ def comp_finish(enum)
221
+ lazy = @comp_lazy.pop
222
+ lazy ? LazyList.from(enum) : enum.to_a
223
+ end
224
+ end
225
+
226
+ # ---- the Prelude proper ----------------------------------------------------
227
+
228
+ defn(:id, 1) { |x| x }
229
+ defn(:const, 2) { |x, _y| x }
230
+ defn(:flip, 3) { |f, x, y| f.(y).(x) }
231
+ defn(:seq, 2) { |_a, b| b }
232
+ defn(:fst, 1) { |p| p[0] }
233
+ defn(:snd, 1) { |p| p[1] }
234
+ defn(:curry, 3) { |f, a, b| f.([a, b]) }
235
+ defn(:uncurry, 2) { |f, p| f.(p[0]).(p[1]) }
236
+ defn(:not, 1) { |b| !b }
237
+ defn(:otherwise, 0) { true }
238
+ defn(:error, 1) { |msg| raise HaskellError, msg.to_s }
239
+ defn(:undefined, 0) { raise HaskellError, "Prelude.undefined" }
240
+ defn(:show, 1) { |x| x.is_a?(String) ? x.inspect : x.inspect }
241
+ defn(:putStrLn, 1) { |s| puts s; [] }
242
+ defn(:putStr, 1) { |s| print s; [] }
243
+ defn(:print, 1) { |x| puts show(x); [] }
244
+
245
+ # numbers
246
+ defn(:even, 1) { |n| n.even? }
247
+ defn(:odd, 1) { |n| n.odd? }
248
+ defn(:negate, 1) { |n| -n }
249
+ defn(:abs, 1) { |n| n.abs }
250
+ defn(:signum, 1) { |n| n <=> 0 }
251
+ defn(:succ, 1) { |x| x.is_a?(String) ? (x.ord + 1).chr : x.is_a?(Numeric) ? x + 1 : x.succ }
252
+ defn(:pred, 1) { |x| x.is_a?(String) ? (x.ord - 1).chr : x.is_a?(Numeric) ? x - 1 : x.pred }
253
+ defn(:fromEnum, 1) { |x| x.is_a?(String) ? x.ord : x.is_a?(Integer) ? x : x.from_enum }
254
+ defn(:min, 2) { |a, b| a <= b ? a : b }
255
+ defn(:max, 2) { |a, b| a >= b ? a : b }
256
+ defn(:subtract, 2) { |a, b| b - a }
257
+ defn(:div, 2) { |a, b| a.div(b) }
258
+ defn(:mod, 2) { |a, b| a % b }
259
+ defn(:quot, 2) { |a, b| (a.to_f / b).truncate }
260
+ defn(:rem, 2) { |a, b| a - b * (a.to_f / b).truncate }
261
+ defn(:divMod, 2) { |a, b| a.divmod(b) }
262
+ defn(:quotRem, 2) { |a, b| q = (a.to_f / b).truncate; [q, a - b * q] }
263
+ defn(:gcd, 2) { |a, b| a.gcd(b) }
264
+ defn(:lcm, 2) { |a, b| a.lcm(b) }
265
+ defn(:fromIntegral, 1) { |n| n }
266
+ defn(:fromInteger, 1) { |n| n }
267
+ defn(:toInteger, 1) { |n| n.to_i }
268
+ defn(:realToFrac, 1) { |n| n.to_f }
269
+ defn(:truncate, 1) { |x| x.truncate }
270
+ defn(:round, 1) { |x| x.round(half: :even) }
271
+ defn(:floor, 1) { |x| x.floor }
272
+ defn(:ceiling, 1) { |x| x.ceil }
273
+ defn(:sqrt, 1) { |x| Math.sqrt(x) }
274
+ defn(:exp, 1) { |x| Math.exp(x) }
275
+ defn(:log, 1) { |x| Math.log(x) }
276
+ defn(:sin, 1) { |x| Math.sin(x) }
277
+ defn(:cos, 1) { |x| Math.cos(x) }
278
+ defn(:tan, 1) { |x| Math.tan(x) }
279
+ defn(:pi, 0) { Math::PI }
280
+ defn(:compare, 2) do |a, b|
281
+ case a <=> b
282
+ when -1 then HaskellMatch.constructor("LT")
283
+ when 0 then HaskellMatch.constructor("EQ")
284
+ when 1 then HaskellMatch.constructor("GT")
285
+ else raise HaskellError, "Prelude.compare: #{a.inspect} and #{b.inspect} are not comparable"
286
+ end
287
+ end
288
+
289
+ # characters
290
+ defn(:ord, 1) { |c| c.ord }
291
+ defn(:chr, 1) { |n| n.chr(Encoding::UTF_8) }
292
+ defn(:toUpper, 1) { |c| c.upcase }
293
+ defn(:toLower, 1) { |c| c.downcase }
294
+ defn(:isDigit, 1) { |c| c.match?(/\A\d\z/) }
295
+ defn(:isAlpha, 1) { |c| c.match?(/\A\p{Alpha}\z/) }
296
+ defn(:isSpace, 1) { |c| c.match?(/\A\s\z/) }
297
+ defn(:isUpper, 1) { |c| c.match?(/\A\p{Upper}\z/) }
298
+ defn(:isLower, 1) { |c| c.match?(/\A\p{Lower}\z/) }
299
+ defn(:digitToInt, 1) { |c| c.to_i(16) }
300
+ defn(:intToDigit, 1) { |n| n.to_s(16) }
301
+
302
+ # Maybe
303
+ defn(:maybe, 3) { |d, f, m| just?(m) ? f.(m.fields[0]) : d }
304
+ defn(:fromMaybe, 2) { |d, m| just?(m) ? m.fields[0] : d }
305
+ defn(:isJust, 1) { |m| just?(m) }
306
+ defn(:isNothing, 1) { |m| !just?(m) }
307
+ defn(:listToMaybe, 1) { |xs| xs = lazy_enum(xs).first(1); xs.empty? ? nothing : just(xs[0]) }
308
+ defn(:maybeToList, 1) { |m| just?(m) ? [m.fields[0]] : [] }
309
+ defn(:either, 3) { |f, g, e| e.class.constructor_name == "Left" ? f.(e.fields[0]) : g.(e.fields[0]) }
310
+
311
+ # lists
312
+ defn(:head, 1) { |xs| lazy_enum(xs).first(1).fetch(0) { raise HaskellError, "Prelude.head: empty list" } }
313
+ defn(:tail, 1) do |xs|
314
+ case xs
315
+ when LazyList then xs.tail
316
+ when Enumerator then LazyList.from(xs).tail
317
+ else
318
+ raise HaskellError, "Prelude.tail: empty list" if xs.empty?
319
+
320
+ xs[1..]
321
+ end
322
+ end
323
+ defn(:last, 1) do |xs|
324
+ raise HaskellError, "Prelude.last: empty list" if xs.empty?
325
+
326
+ xs[-1]
327
+ end
328
+ defn(:init, 1) do |xs|
329
+ raise HaskellError, "Prelude.init: empty list" if xs.empty?
330
+
331
+ xs[0...-1]
332
+ end
333
+ defn(:null, 1) { |xs| lazy?(xs) ? lazy_enum(xs).first(1).empty? : xs.empty? }
334
+ defn(:length, 1) { |xs| lazy?(xs) ? lazy_enum(xs).count : xs.length }
335
+ defn(:reverse, 1) { |xs| lazy?(xs) ? lazy_enum(xs).to_a.reverse : xs.reverse }
336
+ defn(:map, 2) { |f, xs| relist(lazy_enum(xs).map { |x| f.(x) }, xs) }
337
+ defn(:filter, 2) { |p, xs| relist(lazy_enum(xs).select { |x| p.(x) }, xs) }
338
+ defn(:concat, 1) { |xss| relist(lazy_enum(xss).flat_map { |xs| lazy_enum(xs) }, xss) }
339
+ defn(:concatMap, 2) { |f, xs| relist(lazy_enum(xs).flat_map { |x| lazy_enum(f.(x)) }, xs) }
340
+ defn(:foldr, 3) { |f, z, xs| lazy_enum(xs).to_a.reverse_each.inject(z) { |acc, x| f.(x).(acc) } }
341
+ defn(:foldl, 3) { |f, z, xs| lazy_enum(xs).inject(z) { |acc, x| f.(acc).(x) } }
342
+ defn(:"foldl'", 3) { |f, z, xs| lazy_enum(xs).inject(z) { |acc, x| f.(acc).(x) } }
343
+ defn(:foldr1, 2) do |f, xs|
344
+ a = lazy_enum(xs).to_a
345
+ raise HaskellError, "Prelude.foldr1: empty list" if a.empty?
346
+
347
+ a[0...-1].reverse_each.inject(a[-1]) { |acc, x| f.(x).(acc) }
348
+ end
349
+ defn(:foldl1, 2) do |f, xs|
350
+ a = lazy_enum(xs).to_a
351
+ raise HaskellError, "Prelude.foldl1: empty list" if a.empty?
352
+
353
+ a[1..].inject(a[0]) { |acc, x| f.(acc).(x) }
354
+ end
355
+ defn(:scanl, 3) do |f, z, xs|
356
+ acc = z
357
+ out = [z]
358
+ lazy_enum(xs).each { |x| acc = f.(acc).(x); out << acc }
359
+ out
360
+ end
361
+ defn(:scanr, 3) do |f, z, xs|
362
+ lazy_enum(xs).to_a.reverse_each.inject([z]) { |out, x| out.unshift(f.(x).(out[0])) }
363
+ end
364
+ defn(:sum, 1) { |xs| lazy_enum(xs).inject(0) { |a, b| a + b } }
365
+ defn(:product, 1) { |xs| lazy_enum(xs).inject(1) { |a, b| a * b } }
366
+ defn(:maximum, 1) { |xs| lazy_enum(xs).max || raise(HaskellError, "Prelude.maximum: empty list") }
367
+ defn(:minimum, 1) { |xs| lazy_enum(xs).min || raise(HaskellError, "Prelude.minimum: empty list") }
368
+ defn(:and, 1) { |xs| lazy_enum(xs).all? }
369
+ defn(:or, 1) { |xs| lazy_enum(xs).any? }
370
+ defn(:all, 2) { |p, xs| lazy_enum(xs).all? { |x| p.(x) } }
371
+ defn(:any, 2) { |p, xs| lazy_enum(xs).any? { |x| p.(x) } }
372
+ defn(:elem, 2) { |x, xs| lazy_enum(xs).include?(x) }
373
+ defn(:notElem, 2) { |x, xs| !lazy_enum(xs).include?(x) }
374
+ defn(:lookup, 2) do |k, pairs|
375
+ found = lazy_enum(pairs).find { |p| p[0] == k }
376
+ found ? just(found[1]) : nothing
377
+ end
378
+ defn(:take, 2) do |n, xs|
379
+ case xs
380
+ when LazyList then xs.take(n)
381
+ when Enumerator then xs.lazy.first(n)
382
+ else xs[0, [n, 0].max] || xs[0, 0]
383
+ end
384
+ end
385
+ defn(:drop, 2) do |n, xs|
386
+ case xs
387
+ when LazyList, Enumerator then LazyList.from(lazy_enum(xs).drop(n))
388
+ else xs[[n, 0].max..] || xs[0, 0]
389
+ end
390
+ end
391
+ defn(:splitAt, 2) { |n, xs| [take(n, xs), drop(n, xs)] }
392
+ defn(:takeWhile, 2) { |p, xs| relist(lazy_enum(xs).take_while { |x| p.(x) }, xs) }
393
+ defn(:dropWhile, 2) { |p, xs| relist(lazy_enum(xs).drop_while { |x| p.(x) }, xs) }
394
+ defn(:span, 2) { |p, xs| [takeWhile(p, xs), dropWhile(p, xs)] }
395
+ defn(:break, 2) { |p, xs| span(->(x) { !p.(x) }, xs) }
396
+ defn(:zipWith, 3) { |f, xs, ys| relist(zip_enum(xs, ys).map { |a, b| f.(a).(b) }, lazy_pair(xs, ys)) }
397
+ defn(:zip3, 3) { |xs, ys, zs| zip_enum(zip_enum(xs, ys), zs).map { |(a, b), c| [a, b, c] }.to_a }
398
+ defn(:unzip, 1) { |ps| a = lazy_enum(ps).to_a; [a.map { |p| p[0] }, a.map { |p| p[1] }] }
399
+ defn(:replicate, 2) { |n, x| Array.new([n, 0].max, x) }
400
+ defn(:iterate, 2) { |f, x| LazyList.iterate(x) { |v| f.(v) } }
401
+ defn(:repeat, 1) { |x| LazyList.repeat(x) }
402
+ defn(:cycle, 1) do |xs|
403
+ a = lazy_enum(xs).to_a
404
+ raise HaskellError, "Prelude.cycle: empty list" if a.empty?
405
+
406
+ LazyList.from(a.cycle)
407
+ end
408
+ defn(:until, 3) do |p, f, x|
409
+ x = f.(x) until p.(x)
410
+ x
411
+ end
412
+ defn(:words, 1) { |s| s.split }
413
+ defn(:unwords, 1) { |ws| ws.join(" ") }
414
+ defn(:lines, 1) { |s| s.split("\n") }
415
+ defn(:unlines, 1) { |ls| ls.map { |l| "#{l}\n" }.join }
416
+ defn(:sort, 1) { |xs| lazy_enum(xs).to_a.sort }
417
+ defn(:sortBy, 2) { |cmp, xs| lazy_enum(xs).to_a.sort { |a, b| cmp.(a).(b) } }
418
+ defn(:nub, 1) { |xs| relist(lazy_enum(xs).uniq, xs) }
419
+ defn(:partition, 2) { |p, xs| lazy_enum(xs).to_a.partition { |x| p.(x) } }
420
+ defn(:find, 2) { |p, xs| x = lazy_enum(xs).find { |v| p.(v) }; x.nil? ? nothing : just(x) }
421
+
422
+ class << self
423
+ # zip stops at the shorter list; works with one infinite side
424
+ def zip_enum(xs, ys)
425
+ a = lazy_enum(xs)
426
+ b = lazy_enum(ys)
427
+ Enumerator.new do |y|
428
+ ea = a.each
429
+ eb = b.each
430
+ loop { y << [ea.next, eb.next] }
431
+ end.lazy
432
+ end
433
+
434
+ def lazy_pair(xs, ys)
435
+ lazy?(xs) && lazy?(ys) ? xs : (lazy?(xs) ? ys : xs)
436
+ end
437
+ end
438
+ ARITY[:zip] = 2
439
+ defn(:zip, 2) { |xs, ys| relist(zip_enum(xs, ys), lazy_pair(xs, ys)) }
440
+ end
441
+ # The standard types every Haskell program can use. They live under
442
+ # `HaskellMatch::Prelude` (`Prelude::Maybe::Just`); a program may redeclare
443
+ # one, which replaces the registration like any other `HaskellMatch.data`.
444
+ HaskellMatch.data "Maybe a = Nothing | Just a", under: Prelude
445
+ HaskellMatch.data "Either a b = Left a | Right b", under: Prelude
446
+ HaskellMatch.data "Ordering = LT | EQ | GT", under: Prelude
447
+ end
448
+
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ module HaskellMatch
4
+ # Type scopes. Scope 0 is the global registry that `HaskellMatch.data`
5
+ # fills by default and every pattern compiles against unless told
6
+ # otherwise. A compiled Haskell module gets a scope of its own
7
+ # ({Haskell#haskell_scope}): a snapshot of the global registry at the time
8
+ # it is created, plus the module's own `data` declarations, so two modules
9
+ # can each declare a `Shape` without interfering.
10
+ module Native
11
+ GLOBAL_SCOPE = 0
12
+
13
+ class << self
14
+ def register_type(name, specs, scope = GLOBAL_SCOPE)
15
+ register_type_in(name, specs, scope)
16
+ end
17
+
18
+ def render_pattern(source, scope = GLOBAL_SCOPE)
19
+ render_pattern_in(source, scope)
20
+ end
21
+
22
+ def constructors(type_name, scope = GLOBAL_SCOPE)
23
+ constructors_in(type_name, scope)
24
+ end
25
+
26
+ def constructor_info(name, scope = GLOBAL_SCOPE)
27
+ constructor_info_in(name, scope)
28
+ end
29
+
30
+ # Copy the user types of scope `src` (all, or those named) into `dst`;
31
+ # returns the names imported.
32
+ def import_scope(dst, src, names = nil)
33
+ import_scope_in(dst, src, names)
34
+ end
35
+ end
36
+ end
37
+
38
+ class << self
39
+ # A fresh type scope seeded from the global registry.
40
+ def new_scope
41
+ Native.new_scope
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module HaskellMatch
4
+ VERSION = "0.1.0"
5
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Haskell-style pattern matching for Ruby, compiled to decision trees in Rust.
4
+ #
5
+ # require "haskell_match"
6
+ #
7
+ # HaskellMatch.data "Maybe a = Nothing | Just a"
8
+ # include Maybe
9
+ #
10
+ # from_maybe = HaskellMatch.fn(:from_maybe) do
11
+ # on("d", "Nothing") { |d| d }
12
+ # on("_", "Just x") { |x| x }
13
+ # end
14
+ #
15
+ # from_maybe.(0, Just.new(5)) # => 5
16
+ # from_maybe.(0, Nothing) # => 0
17
+ module HaskellMatch
18
+ end
19
+
20
+ require_relative "haskell_match/version"
21
+ require_relative "haskell_match/errors"
22
+ require_relative "haskell_match/native_loader"
23
+ require_relative "haskell_match/scope"
24
+
25
+ HaskellMatch::NativeLoader.load!
26
+
27
+ require_relative "haskell_match/inspect"
28
+ require_relative "haskell_match/data"
29
+ require_relative "haskell_match/deriving"
30
+ require_relative "haskell_match/field_types"
31
+ require_relative "haskell_match/lazy_list"
32
+ require_relative "haskell_match/pattern_ast"
33
+ require_relative "haskell_match/binding_plan"
34
+ require_relative "haskell_match/clauses"
35
+ require_relative "haskell_match/deep_call"
36
+ require_relative "haskell_match/function"
37
+ require_relative "haskell_match/case_of"
38
+ require_relative "haskell_match/pattern"
39
+ require_relative "haskell_match/dsl"
40
+ require_relative "haskell_match/prelude"
41
+ require_relative "haskell_match/haskell"
metadata ADDED
@@ -0,0 +1,124 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: haskell_match
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Daniel P. Clark
8
+ bindir: bin
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: minitest
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - "~>"
17
+ - !ruby/object:Gem::Version
18
+ version: '5.0'
19
+ type: :development
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - "~>"
24
+ - !ruby/object:Gem::Version
25
+ version: '5.0'
26
+ - !ruby/object:Gem::Dependency
27
+ name: rake
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - "~>"
31
+ - !ruby/object:Gem::Version
32
+ version: '13.0'
33
+ type: :development
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: '13.0'
40
+ description: |
41
+ Algebraic data types and Haskell pattern syntax for Ruby. Clauses are
42
+ compiled once into a decision tree by a Rust extension (via Rutie) and
43
+ checked for exhaustiveness and redundancy, so every logical path is
44
+ accounted for before the first call.
45
+ email:
46
+ - 6ftdan@gmail.com
47
+ executables: []
48
+ extensions:
49
+ - ext/haskell_match/extconf.rb
50
+ extra_rdoc_files: []
51
+ files:
52
+ - CHANGELOG.md
53
+ - LICENSE-APACHE
54
+ - LICENSE-MIT
55
+ - README.md
56
+ - ext/haskell_match/Cargo.lock
57
+ - ext/haskell_match/Cargo.toml
58
+ - ext/haskell_match/extconf.rb
59
+ - ext/haskell_match/src/core/ast.rs
60
+ - ext/haskell_match/src/core/error.rs
61
+ - ext/haskell_match/src/core/exhaust.rs
62
+ - ext/haskell_match/src/core/hs/ast.rs
63
+ - ext/haskell_match/src/core/hs/json.rs
64
+ - ext/haskell_match/src/core/hs/layout.rs
65
+ - ext/haskell_match/src/core/hs/lexer.rs
66
+ - ext/haskell_match/src/core/hs/mod.rs
67
+ - ext/haskell_match/src/core/hs/parser.rs
68
+ - ext/haskell_match/src/core/lexer.rs
69
+ - ext/haskell_match/src/core/mod.rs
70
+ - ext/haskell_match/src/core/parser.rs
71
+ - ext/haskell_match/src/core/pretty.rs
72
+ - ext/haskell_match/src/core/resolve.rs
73
+ - ext/haskell_match/src/core/tree.rs
74
+ - ext/haskell_match/src/core/typecheck.rs
75
+ - ext/haskell_match/src/core/types.rs
76
+ - ext/haskell_match/src/lib.rs
77
+ - ext/haskell_match/src/ruby/mod.rs
78
+ - ext/haskell_match/src/ruby/runtime.rs
79
+ - lib/haskell_match.rb
80
+ - lib/haskell_match/binding_plan.rb
81
+ - lib/haskell_match/case_of.rb
82
+ - lib/haskell_match/clauses.rb
83
+ - lib/haskell_match/data.rb
84
+ - lib/haskell_match/deep_call.rb
85
+ - lib/haskell_match/deriving.rb
86
+ - lib/haskell_match/dsl.rb
87
+ - lib/haskell_match/errors.rb
88
+ - lib/haskell_match/field_types.rb
89
+ - lib/haskell_match/function.rb
90
+ - lib/haskell_match/haskell.rb
91
+ - lib/haskell_match/haskell/compiler.rb
92
+ - lib/haskell_match/inspect.rb
93
+ - lib/haskell_match/lazy_list.rb
94
+ - lib/haskell_match/native_loader.rb
95
+ - lib/haskell_match/pattern.rb
96
+ - lib/haskell_match/pattern_ast.rb
97
+ - lib/haskell_match/prelude.rb
98
+ - lib/haskell_match/scope.rb
99
+ - lib/haskell_match/version.rb
100
+ homepage: https://github.com/danielpclark/haskell_match
101
+ licenses:
102
+ - MIT
103
+ - Apache-2.0
104
+ metadata:
105
+ source_code_uri: https://github.com/danielpclark/haskell_match
106
+ rubygems_mfa_required: 'true'
107
+ rdoc_options: []
108
+ require_paths:
109
+ - lib
110
+ required_ruby_version: !ruby/object:Gem::Requirement
111
+ requirements:
112
+ - - ">="
113
+ - !ruby/object:Gem::Version
114
+ version: '3.2'
115
+ required_rubygems_version: !ruby/object:Gem::Requirement
116
+ requirements:
117
+ - - ">="
118
+ - !ruby/object:Gem::Version
119
+ version: '0'
120
+ requirements: []
121
+ rubygems_version: 3.6.9
122
+ specification_version: 4
123
+ summary: Haskell-style pattern matching for Ruby, compiled to decision trees in Rust
124
+ test_files: []