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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +98 -0
- data/LICENSE-APACHE +202 -0
- data/LICENSE-MIT +21 -0
- data/README.md +1484 -0
- data/ext/haskell_match/Cargo.lock +33 -0
- data/ext/haskell_match/Cargo.toml +22 -0
- data/ext/haskell_match/extconf.rb +41 -0
- data/ext/haskell_match/src/core/ast.rs +190 -0
- data/ext/haskell_match/src/core/error.rs +52 -0
- data/ext/haskell_match/src/core/exhaust.rs +699 -0
- data/ext/haskell_match/src/core/hs/ast.rs +256 -0
- data/ext/haskell_match/src/core/hs/json.rs +225 -0
- data/ext/haskell_match/src/core/hs/layout.rs +346 -0
- data/ext/haskell_match/src/core/hs/lexer.rs +688 -0
- data/ext/haskell_match/src/core/hs/mod.rs +14 -0
- data/ext/haskell_match/src/core/hs/parser.rs +1945 -0
- data/ext/haskell_match/src/core/lexer.rs +590 -0
- data/ext/haskell_match/src/core/mod.rs +19 -0
- data/ext/haskell_match/src/core/parser.rs +1116 -0
- data/ext/haskell_match/src/core/pretty.rs +373 -0
- data/ext/haskell_match/src/core/resolve.rs +336 -0
- data/ext/haskell_match/src/core/tree.rs +921 -0
- data/ext/haskell_match/src/core/typecheck.rs +226 -0
- data/ext/haskell_match/src/core/types.rs +404 -0
- data/ext/haskell_match/src/lib.rs +19 -0
- data/ext/haskell_match/src/ruby/mod.rs +1195 -0
- data/ext/haskell_match/src/ruby/runtime.rs +1045 -0
- data/lib/haskell_match/binding_plan.rb +84 -0
- data/lib/haskell_match/case_of.rb +71 -0
- data/lib/haskell_match/clauses.rb +354 -0
- data/lib/haskell_match/data.rb +417 -0
- data/lib/haskell_match/deep_call.rb +98 -0
- data/lib/haskell_match/deriving.rb +130 -0
- data/lib/haskell_match/dsl.rb +71 -0
- data/lib/haskell_match/errors.rb +85 -0
- data/lib/haskell_match/field_types.rb +140 -0
- data/lib/haskell_match/function.rb +240 -0
- data/lib/haskell_match/haskell/compiler.rb +961 -0
- data/lib/haskell_match/haskell.rb +326 -0
- data/lib/haskell_match/inspect.rb +45 -0
- data/lib/haskell_match/lazy_list.rb +210 -0
- data/lib/haskell_match/native_loader.rb +64 -0
- data/lib/haskell_match/pattern.rb +75 -0
- data/lib/haskell_match/pattern_ast.rb +394 -0
- data/lib/haskell_match/prelude.rb +448 -0
- data/lib/haskell_match/scope.rb +44 -0
- data/lib/haskell_match/version.rb +5 -0
- data/lib/haskell_match.rb +41 -0
- 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,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: []
|