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,417 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module HaskellMatch
|
|
4
|
+
# Mixin for every constructor class produced by {HaskellMatch.data}.
|
|
5
|
+
# Instances are frozen `Data` values; the class knows its type module,
|
|
6
|
+
# constructor name and field names.
|
|
7
|
+
module Constructor
|
|
8
|
+
module ClassMethods
|
|
9
|
+
attr_reader :data_type, :constructor_name, :field_names, :arity
|
|
10
|
+
|
|
11
|
+
# `Just.(5)` -- Haskell constructors are functions.
|
|
12
|
+
def call(*args, **kwargs)
|
|
13
|
+
new(*args, **kwargs)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def to_proc
|
|
17
|
+
method(:new).to_proc
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def record?
|
|
21
|
+
!field_names.nil?
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def nullary?
|
|
25
|
+
arity.zero?
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# The nullary singleton value, or nil for constructors with fields.
|
|
29
|
+
def value
|
|
30
|
+
@value
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Declared field types, as written (`["String", "Int"]`), or nil.
|
|
34
|
+
def field_types
|
|
35
|
+
@field_types
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Class-level information for Ruby classes made constructors of a type by
|
|
40
|
+
# {HaskellMatch.sealed}; readers only, nothing of the class's behaviour
|
|
41
|
+
# changes.
|
|
42
|
+
module SealedClassMethods
|
|
43
|
+
attr_reader :data_type, :constructor_name, :field_names, :arity
|
|
44
|
+
|
|
45
|
+
def record?
|
|
46
|
+
!field_names.nil?
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def nullary?
|
|
50
|
+
arity.zero?
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Sealed classes have no singleton value; `constructors` lists the class.
|
|
54
|
+
def value
|
|
55
|
+
nil
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def field_types
|
|
59
|
+
nil
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def self.included(base)
|
|
64
|
+
base.extend(ClassMethods)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# Field values in declaration order.
|
|
68
|
+
def fields
|
|
69
|
+
to_h.values
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def [](index)
|
|
73
|
+
case index
|
|
74
|
+
when Integer then fields[index]
|
|
75
|
+
else public_send(index)
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def constructor_name
|
|
80
|
+
self.class.constructor_name
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def data_type
|
|
84
|
+
self.class.data_type
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def inspect
|
|
88
|
+
Inspect.render(self)
|
|
89
|
+
end
|
|
90
|
+
alias to_s inspect
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# The module returned by {HaskellMatch.data}; it holds a constant per
|
|
94
|
+
# constructor so `include Maybe` brings `Just` and `Nothing` into scope.
|
|
95
|
+
module DataType
|
|
96
|
+
attr_reader :type_name, :type_variables, :constructor_classes
|
|
97
|
+
|
|
98
|
+
# Constructors in declaration order: classes for constructors with
|
|
99
|
+
# fields, singleton values for nullary ones (classes for nullary sealed
|
|
100
|
+
# classes, which have no singleton).
|
|
101
|
+
def constructors
|
|
102
|
+
constructor_classes.map { |k| k.value || k }
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def constructor_names
|
|
106
|
+
constructor_classes.map(&:constructor_name)
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# `Maybe === Just.new(1)` -> true
|
|
110
|
+
def ===(value)
|
|
111
|
+
k = value.class
|
|
112
|
+
k.respond_to?(:data_type) && k.data_type.equal?(self)
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# Haskell keeps types and constructors in separate namespaces; for
|
|
116
|
+
# `data Person = Person {...}` the type module answers `new`, `[]` and
|
|
117
|
+
# `call` with the same-named constructor, so `Person.new("Al", 3)` works
|
|
118
|
+
# whether `Person` names the type or the constructor.
|
|
119
|
+
def new(*args, **kwargs)
|
|
120
|
+
same_named_constructor.new(*args, **kwargs)
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def [](*args)
|
|
124
|
+
same_named_constructor[*args]
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def call(*args)
|
|
128
|
+
same_named_constructor.call(*args)
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def same_named_constructor
|
|
132
|
+
k = constructor_classes.find { |c| c.constructor_name == type_name }
|
|
133
|
+
raise NoMethodError, "#{inspect} has no constructor named #{type_name}" if k.nil?
|
|
134
|
+
raise NoMethodError, "#{type_name} is a nullary constructor: use #{type_name}::#{type_name}" if k.nullary?
|
|
135
|
+
|
|
136
|
+
k
|
|
137
|
+
end
|
|
138
|
+
private :same_named_constructor
|
|
139
|
+
|
|
140
|
+
def inspect
|
|
141
|
+
"data #{[type_name, *type_variables].join(' ')} = " +
|
|
142
|
+
constructor_classes.map do |k|
|
|
143
|
+
if k.record?
|
|
144
|
+
"#{k.constructor_name} {#{k.field_names.join(', ')}}"
|
|
145
|
+
elsif k.constructor_name.start_with?(":") && k.arity == 2
|
|
146
|
+
"_ #{k.constructor_name} _"
|
|
147
|
+
else
|
|
148
|
+
[k.constructor_name, *Array.new(k.arity, "_")].join(" ")
|
|
149
|
+
end
|
|
150
|
+
end.join(" | ")
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
class << self
|
|
155
|
+
SYMBOL_WORDS = {
|
|
156
|
+
":" => "Colon", "+" => "Plus", "-" => "Minus", "*" => "Star", "/" => "Slash", "<" => "Lt", ">" => "Gt",
|
|
157
|
+
"=" => "Eq", "!" => "Bang", "@" => "At", "#" => "Hash", "$" => "Dollar", "%" => "Percent", "&" => "Amp",
|
|
158
|
+
"^" => "Caret", "|" => "Bar", "~" => "Tilde", "?" => "Query", "." => "Dot", "\\" => "Backslash"
|
|
159
|
+
}.freeze
|
|
160
|
+
|
|
161
|
+
# The Ruby constant for a constructor: its own name, or for an infix
|
|
162
|
+
# constructor such as `:+:` a spelled-out one (`ColonPlusColon`).
|
|
163
|
+
def constructor_constant(cname)
|
|
164
|
+
return cname unless cname.start_with?(":")
|
|
165
|
+
|
|
166
|
+
const = cname.chars.map { |c| SYMBOL_WORDS.fetch(c) { "U#{c.ord}" } }.join
|
|
167
|
+
(@symbolic_constants ||= {})[const] = cname
|
|
168
|
+
const
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
# The Haskell name of an infix constructor from its spelled-out constant
|
|
172
|
+
# (`ColonPlusColon` -> `:+:`), or nil.
|
|
173
|
+
def symbolic_constructor(const_name)
|
|
174
|
+
(@symbolic_constants ||= {})[const_name.to_s]
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
# Copy the registrations of `type_names` from scope `src` into `dst`
|
|
178
|
+
# (the Ruby side of `Native.import_scope`).
|
|
179
|
+
def import_constructors(dst, src, type_names)
|
|
180
|
+
@constructors ||= {}
|
|
181
|
+
@type_modules ||= {}
|
|
182
|
+
(@constructors[src] || {}).each do |n, c|
|
|
183
|
+
k = c.is_a?(Class) ? c : c.class
|
|
184
|
+
(@constructors[dst] ||= {})[n] = c if k.respond_to?(:data_type) && type_names.include?(k.data_type.type_name)
|
|
185
|
+
end
|
|
186
|
+
(@type_modules[src] || {}).each do |n, m|
|
|
187
|
+
(@type_modules[dst] ||= {})[n] = m if type_names.include?(n)
|
|
188
|
+
end
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
# Constructor classes and nullary values by name, as currently registered
|
|
192
|
+
# (a redefined type replaces its constructors).
|
|
193
|
+
def constructor(name, scope = Native::GLOBAL_SCOPE)
|
|
194
|
+
@constructors ||= {}
|
|
195
|
+
(@constructors[scope] || {})[name.to_s] || (scope != Native::GLOBAL_SCOPE ? constructor(name) : nil)
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
def register_constructors(classes, scope = Native::GLOBAL_SCOPE)
|
|
199
|
+
@constructors ||= {}
|
|
200
|
+
table = (@constructors[scope] ||= {})
|
|
201
|
+
classes.each { |k| table[k.constructor_name] = k.value || k }
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
# The type module registered under `name` (in `scope`, falling back to
|
|
205
|
+
# the global scope), or nil.
|
|
206
|
+
def type_module(name, scope = Native::GLOBAL_SCOPE)
|
|
207
|
+
@type_modules ||= {}
|
|
208
|
+
(@type_modules[scope] || {})[name.to_s] || (scope != Native::GLOBAL_SCOPE ? type_module(name) : nil)
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
# Whether `HaskellMatch.data` checks field types by default (see
|
|
212
|
+
# {FieldTypes}); off unless set.
|
|
213
|
+
attr_writer :check_field_types
|
|
214
|
+
|
|
215
|
+
def check_field_types
|
|
216
|
+
@check_field_types ? true : false
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
# Declare an algebraic data type.
|
|
220
|
+
#
|
|
221
|
+
# HaskellMatch.data "Maybe a = Nothing | Just a"
|
|
222
|
+
# HaskellMatch.data "Shape = Circle Double | Rect Double Double"
|
|
223
|
+
# HaskellMatch.data "Person = Person { name :: String, age :: Int }"
|
|
224
|
+
# HaskellMatch.data :Maybe, Nothing: 0, Just: 1 # arities
|
|
225
|
+
# HaskellMatch.data :Person, Person: { name: :String, age: :Int }
|
|
226
|
+
#
|
|
227
|
+
# Returns a module with one constant per constructor and defines it as a
|
|
228
|
+
# constant named after the type under `under` (default: `Object`, i.e. a
|
|
229
|
+
# top-level constant, like a Haskell `data` declaration). Pass
|
|
230
|
+
# `under: nil` to skip the constant definition. `scope:` registers the
|
|
231
|
+
# type in a type scope other than the global one (see {Native::GLOBAL_SCOPE}
|
|
232
|
+
# and {Haskell#haskell_scope}). Constructors with fields
|
|
233
|
+
# are `Data` subclasses (`Just.new(1)`, `Just[1]`, `Just.(1)`); nullary
|
|
234
|
+
# constructors are frozen singleton values (`Nothing`).
|
|
235
|
+
def data(decl, under: Object, scope: Native::GLOBAL_SCOPE, check_types: check_field_types, **constructors)
|
|
236
|
+
name, tyvars, specs, deriving = normalize_data(decl, constructors)
|
|
237
|
+
validate_type_name!(name)
|
|
238
|
+
Deriving.validate!(name, specs, deriving)
|
|
239
|
+
mod = new_type_module(name, tyvars, under)
|
|
240
|
+
|
|
241
|
+
classes = specs.map do |cname, arity, fields, types|
|
|
242
|
+
members = fields ? fields.map(&:to_sym) : (1..arity).map { |i| :"_#{i}" }
|
|
243
|
+
klass = Data.define(*members)
|
|
244
|
+
klass.include(Constructor)
|
|
245
|
+
klass.instance_variable_set(:@data_type, mod)
|
|
246
|
+
klass.instance_variable_set(:@constructor_name, cname)
|
|
247
|
+
klass.instance_variable_set(:@field_names, fields&.map(&:to_sym))
|
|
248
|
+
klass.instance_variable_set(:@arity, arity)
|
|
249
|
+
klass.instance_variable_set(:@field_types, types&.map(&:to_s))
|
|
250
|
+
const = constructor_constant(cname)
|
|
251
|
+
mod.const_set(const, klass)
|
|
252
|
+
klass.name # cache the permanent name
|
|
253
|
+
FieldTypes.install(klass, types.map(&:to_s), scope) if check_types && types && types.size == arity && arity.positive?
|
|
254
|
+
if arity.zero?
|
|
255
|
+
value = klass.new.freeze
|
|
256
|
+
klass.instance_variable_set(:@value, value)
|
|
257
|
+
klass.singleton_class.send(:undef_method, :new)
|
|
258
|
+
klass.singleton_class.send(:undef_method, :[])
|
|
259
|
+
klass.singleton_class.send(:undef_method, :call)
|
|
260
|
+
mod.send(:remove_const, const)
|
|
261
|
+
mod.const_set(const, value)
|
|
262
|
+
end
|
|
263
|
+
klass
|
|
264
|
+
end
|
|
265
|
+
finish_type(mod, classes, under, scope)
|
|
266
|
+
Deriving.apply(mod, classes, deriving)
|
|
267
|
+
mod
|
|
268
|
+
end
|
|
269
|
+
|
|
270
|
+
# Make existing Ruby classes the constructors of a closed type, so their
|
|
271
|
+
# instances match constructor patterns with full exhaustiveness checking
|
|
272
|
+
# and no `HaskellMatch.data` declaration:
|
|
273
|
+
#
|
|
274
|
+
# Circle = Data.define(:r)
|
|
275
|
+
# Rect = Data.define(:w, :h)
|
|
276
|
+
# HaskellMatch.sealed :Shape, Circle, Rect
|
|
277
|
+
# area = HaskellMatch.fn(:area) { on(Circle(r)) { |r| 3 * r * r }; on("Rect w h") { |w, h| w * h } }
|
|
278
|
+
#
|
|
279
|
+
# `Data` and `Struct` classes supply their own field names (so record
|
|
280
|
+
# patterns `Circle { r = x }` work too); for any other class pass the
|
|
281
|
+
# reader methods that are its fields:
|
|
282
|
+
#
|
|
283
|
+
# HaskellMatch.sealed :Shape, Circle => [:r], Rect => [:w, :h]
|
|
284
|
+
#
|
|
285
|
+
# The constructor name is the class's own name (`Geometry::Circle` is the
|
|
286
|
+
# constructor `Circle`). Instances are identified by exact class, so list
|
|
287
|
+
# the leaf classes of a hierarchy. Returns the type module, defined as a
|
|
288
|
+
# constant under `under` when given (default: none).
|
|
289
|
+
def sealed(name, *classes, under: nil, scope: Native::GLOBAL_SCOPE, **with_fields)
|
|
290
|
+
name = name.to_s
|
|
291
|
+
validate_type_name!(name)
|
|
292
|
+
with_fields = with_fields.merge(classes.pop) if classes.last.is_a?(Hash)
|
|
293
|
+
entries = classes.map { |k| [k, nil] } + with_fields.map { |k, f| [k, Array(f)] }
|
|
294
|
+
raise DataDeclarationError, "type '#{name}' must have at least one constructor class" if entries.empty?
|
|
295
|
+
|
|
296
|
+
mod = new_type_module(name, [], under)
|
|
297
|
+
cons = entries.map do |klass, fields|
|
|
298
|
+
raise DataDeclarationError, "#{klass.inspect} is not a Class" unless klass.is_a?(Class)
|
|
299
|
+
|
|
300
|
+
cname = klass.name&.split("::")&.last
|
|
301
|
+
if cname.nil? || !cname.match?(/\A[A-Z][A-Za-z0-9_']*\z/)
|
|
302
|
+
raise DataDeclarationError, "#{klass.inspect} needs a constant name starting with an upper-case letter"
|
|
303
|
+
end
|
|
304
|
+
if klass.singleton_class.include?(Constructor::SealedClassMethods) || klass.include?(Constructor)
|
|
305
|
+
raise DataDeclarationError, "#{klass} is already a constructor of type '#{klass.data_type.type_name}'"
|
|
306
|
+
end
|
|
307
|
+
|
|
308
|
+
fields ||= klass.members.map(&:to_s) if klass.respond_to?(:members)
|
|
309
|
+
if fields.nil?
|
|
310
|
+
raise DataDeclarationError,
|
|
311
|
+
"#{klass} is neither a Data nor a Struct class: list its field readers (#{cname} => [:a, :b])"
|
|
312
|
+
end
|
|
313
|
+
fields = fields.map(&:to_s)
|
|
314
|
+
klass.extend(Constructor::SealedClassMethods)
|
|
315
|
+
klass.instance_variable_set(:@data_type, mod)
|
|
316
|
+
klass.instance_variable_set(:@constructor_name, cname)
|
|
317
|
+
klass.instance_variable_set(:@field_names, fields.map(&:to_sym))
|
|
318
|
+
klass.instance_variable_set(:@arity, fields.size)
|
|
319
|
+
mod.const_set(cname, klass)
|
|
320
|
+
klass
|
|
321
|
+
end
|
|
322
|
+
begin
|
|
323
|
+
finish_type(mod, cons, under, scope)
|
|
324
|
+
rescue CompileError
|
|
325
|
+
cons.each do |k|
|
|
326
|
+
%i[@data_type @constructor_name @field_names @arity].each { |iv| k.remove_instance_variable(iv) }
|
|
327
|
+
k.singleton_class.send(:undef_method, *Constructor::SealedClassMethods.instance_methods(false)) rescue nil # rubocop:disable Style/RescueModifier
|
|
328
|
+
end
|
|
329
|
+
raise
|
|
330
|
+
end
|
|
331
|
+
mod
|
|
332
|
+
end
|
|
333
|
+
|
|
334
|
+
private
|
|
335
|
+
|
|
336
|
+
def new_type_module(name, tyvars, under)
|
|
337
|
+
mod = Module.new
|
|
338
|
+
mod.extend(DataType)
|
|
339
|
+
mod.instance_variable_set(:@type_name, name)
|
|
340
|
+
mod.instance_variable_set(:@type_variables, tyvars)
|
|
341
|
+
mod.instance_variable_set(:@constructor_classes, [])
|
|
342
|
+
# Name the module first so nullary constructor classes keep a permanent
|
|
343
|
+
# class path even after their constant is replaced by the singleton.
|
|
344
|
+
home = under || Types
|
|
345
|
+
home.send(:remove_const, name) if home.const_defined?(name, false)
|
|
346
|
+
home.const_set(name, mod)
|
|
347
|
+
mod
|
|
348
|
+
end
|
|
349
|
+
|
|
350
|
+
# Register the type natively and in the Ruby registries; on failure the
|
|
351
|
+
# constant is removed again.
|
|
352
|
+
def finish_type(mod, classes, under, scope)
|
|
353
|
+
mod.instance_variable_get(:@constructor_classes).concat(classes).freeze
|
|
354
|
+
name = mod.type_name
|
|
355
|
+
begin
|
|
356
|
+
Native.register_type(name, classes.map { |k| [k.constructor_name, k.arity, k.field_names&.map(&:to_s), k] }, scope)
|
|
357
|
+
rescue CompileError
|
|
358
|
+
home = under || Types
|
|
359
|
+
home.send(:remove_const, name) if home.const_defined?(name, false) && home.const_get(name, false).equal?(mod)
|
|
360
|
+
raise
|
|
361
|
+
end
|
|
362
|
+
register_constructors(classes, scope)
|
|
363
|
+
@type_modules ||= {}
|
|
364
|
+
(@type_modules[scope] ||= {})[name] = mod
|
|
365
|
+
mod
|
|
366
|
+
end
|
|
367
|
+
|
|
368
|
+
def normalize_data(decl, constructors)
|
|
369
|
+
if decl.is_a?(String)
|
|
370
|
+
unless constructors.empty?
|
|
371
|
+
raise ArgumentError, "pass either a declaration string or constructor keywords, not both"
|
|
372
|
+
end
|
|
373
|
+
name, tyvars, cons, deriving = Native.parse_data(decl)
|
|
374
|
+
[name, tyvars, cons, deriving]
|
|
375
|
+
else
|
|
376
|
+
name = decl.to_s
|
|
377
|
+
deriving = Array(constructors.delete(:deriving)).map(&:to_s)
|
|
378
|
+
if constructors.empty?
|
|
379
|
+
raise DataDeclarationError, "type '#{name}' must have at least one constructor"
|
|
380
|
+
end
|
|
381
|
+
specs = constructors.map do |cname, spec|
|
|
382
|
+
case spec
|
|
383
|
+
when Integer
|
|
384
|
+
raise DataDeclarationError, "arity of '#{cname}' must not be negative" if spec.negative?
|
|
385
|
+
|
|
386
|
+
[cname.to_s, spec, nil, []]
|
|
387
|
+
when Array
|
|
388
|
+
[cname.to_s, spec.size, nil, spec.map(&:to_s)]
|
|
389
|
+
when Hash
|
|
390
|
+
fields = spec.keys.map(&:to_s)
|
|
391
|
+
fields.each do |f|
|
|
392
|
+
unless f.match?(/\A[a-z_][A-Za-z0-9_']*\z/)
|
|
393
|
+
raise DataDeclarationError, "field name '#{f}' must start with a lower-case letter"
|
|
394
|
+
end
|
|
395
|
+
end
|
|
396
|
+
[cname.to_s, fields.size, fields, spec.values.map(&:to_s)]
|
|
397
|
+
when nil
|
|
398
|
+
[cname.to_s, 0, nil, []]
|
|
399
|
+
else
|
|
400
|
+
raise DataDeclarationError,
|
|
401
|
+
"constructor '#{cname}' must be given an arity, an Array of field types or a Hash of fields"
|
|
402
|
+
end
|
|
403
|
+
end
|
|
404
|
+
[name, [], specs, deriving]
|
|
405
|
+
end
|
|
406
|
+
end
|
|
407
|
+
|
|
408
|
+
def validate_type_name!(name)
|
|
409
|
+
return if name.match?(/\A[A-Z][A-Za-z0-9_']*\z/)
|
|
410
|
+
|
|
411
|
+
raise DataDeclarationError, "type name '#{name}' must start with an upper-case letter"
|
|
412
|
+
end
|
|
413
|
+
end
|
|
414
|
+
|
|
415
|
+
# Home for types declared with `under: nil`.
|
|
416
|
+
module Types; end
|
|
417
|
+
end
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module HaskellMatch
|
|
4
|
+
# Deep mode: `call` implemented in Ruby, so that while a recursion is
|
|
5
|
+
# pending no native frame sits on the machine stack. Each level then costs
|
|
6
|
+
# only its Ruby VM frames (a few hundred bytes) and the GC scans only those,
|
|
7
|
+
# which is what makes very deep recursion cheap. The price is one extra
|
|
8
|
+
# Ruby method frame per call (see the README's expert notes).
|
|
9
|
+
#
|
|
10
|
+
# The native `prepare` does the matching, counts the nesting level and
|
|
11
|
+
# returns `[values..., body, depth]`; the generated `call` invokes the body
|
|
12
|
+
# (on a fresh Fiber at segment boundaries), pops the level, and hands any
|
|
13
|
+
# `tail`/`defer` marker to the trampoline below, which has the same
|
|
14
|
+
# semantics as the native `call`.
|
|
15
|
+
module DeepCall
|
|
16
|
+
CONT_CHUNK = 256
|
|
17
|
+
|
|
18
|
+
class << self
|
|
19
|
+
# Mirrors HaskellMatch.stack_segment (kept here to avoid a native call
|
|
20
|
+
# per invocation).
|
|
21
|
+
attr_accessor :segment
|
|
22
|
+
end
|
|
23
|
+
self.segment = Native.stack_segment
|
|
24
|
+
|
|
25
|
+
# Generate a `call` of exact arity (avoids a splat allocation per call).
|
|
26
|
+
def self.module_for(arity)
|
|
27
|
+
(@modules ||= {})[arity] ||= begin
|
|
28
|
+
params = (1..arity).map { |i| "a#{i}" }.join(", ")
|
|
29
|
+
Module.new.tap do |m|
|
|
30
|
+
m.module_eval(<<~RUBY, __FILE__, __LINE__ + 1)
|
|
31
|
+
def call(#{params})
|
|
32
|
+
vals = prepare(#{params})
|
|
33
|
+
depth = vals.pop
|
|
34
|
+
body = vals.pop
|
|
35
|
+
result = begin
|
|
36
|
+
seg = DeepCall.segment
|
|
37
|
+
if seg > 0 && depth % seg == 0
|
|
38
|
+
Fiber.new { body.call(*vals) }.resume
|
|
39
|
+
else
|
|
40
|
+
body.call(*vals)
|
|
41
|
+
end
|
|
42
|
+
ensure
|
|
43
|
+
Native.depth_pop
|
|
44
|
+
end
|
|
45
|
+
TailCall === result ? DeepCall.trampoline(result) : result
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def deep?
|
|
49
|
+
true
|
|
50
|
+
end
|
|
51
|
+
RUBY
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Run `body` with `vals`, on a fresh Fiber at segment boundaries.
|
|
57
|
+
def self.invoke(body, vals, depth)
|
|
58
|
+
seg = segment
|
|
59
|
+
if seg > 0 && (depth % seg).zero?
|
|
60
|
+
Fiber.new { body.call(*vals) }.resume
|
|
61
|
+
else
|
|
62
|
+
body.call(*vals)
|
|
63
|
+
end
|
|
64
|
+
ensure
|
|
65
|
+
Native.depth_pop
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# Continue a call whose body returned a `TailCall` marker: apply tail
|
|
69
|
+
# calls on the spot and keep `defer` continuations on a chunked stack.
|
|
70
|
+
def self.trampoline(result)
|
|
71
|
+
conts = nil
|
|
72
|
+
while true
|
|
73
|
+
if TailCall === result
|
|
74
|
+
if (k = result.continuation)
|
|
75
|
+
conts = [conts] if conts.nil? || conts.size > CONT_CHUNK
|
|
76
|
+
conts << k
|
|
77
|
+
end
|
|
78
|
+
vals = result.function.prepare(*result.args)
|
|
79
|
+
depth = vals.pop
|
|
80
|
+
body = vals.pop
|
|
81
|
+
result = invoke(body, vals, depth)
|
|
82
|
+
else
|
|
83
|
+
k = nil
|
|
84
|
+
while conts
|
|
85
|
+
if conts.size > 1
|
|
86
|
+
k = conts.pop
|
|
87
|
+
break
|
|
88
|
+
end
|
|
89
|
+
conts = conts[0]
|
|
90
|
+
end
|
|
91
|
+
return result unless k
|
|
92
|
+
|
|
93
|
+
result = k.call(result)
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
end
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module HaskellMatch
|
|
4
|
+
# `deriving (...)` on a data declaration. `Eq` and `Show` always hold for
|
|
5
|
+
# constructor values (structural equality and Haskell-style `inspect`);
|
|
6
|
+
# `Ord`, `Enum` and `Bounded` add the corresponding behaviour:
|
|
7
|
+
#
|
|
8
|
+
# HaskellMatch.data "Color = Red | Green | Blue deriving (Eq, Ord, Enum, Bounded)"
|
|
9
|
+
# Red < Blue # => true (Ord: by constructor order, then fields)
|
|
10
|
+
# Red.succ # => Green (Enum)
|
|
11
|
+
# (Red..Blue).to_a # => [Red, Green, Blue]
|
|
12
|
+
# Color.enum_from(Green) # => [Green, Blue]
|
|
13
|
+
# Color.min_bound # => Red (Bounded)
|
|
14
|
+
module Deriving
|
|
15
|
+
SUPPORTED = %w[Eq Show Ord Enum Bounded].freeze
|
|
16
|
+
|
|
17
|
+
module_function
|
|
18
|
+
|
|
19
|
+
# Reject unsupported classes and ill-typed derivations up front, before
|
|
20
|
+
# the type is registered (`specs` are [name, arity, fields, types] rows).
|
|
21
|
+
def validate!(type_name, specs, deriving)
|
|
22
|
+
deriving.each do |cls|
|
|
23
|
+
cls = cls.to_s
|
|
24
|
+
unless SUPPORTED.include?(cls)
|
|
25
|
+
raise DataDeclarationError,
|
|
26
|
+
"cannot derive '#{cls}' for type '#{type_name}' (supported: #{SUPPORTED.join(', ')})"
|
|
27
|
+
end
|
|
28
|
+
next unless %w[Enum Bounded].include?(cls) && specs.any? { |(_, arity, *)| arity.positive? }
|
|
29
|
+
|
|
30
|
+
raise DataDeclarationError,
|
|
31
|
+
"cannot derive '#{cls}' for type '#{type_name}': it must be an enumeration type (all constructors nullary)"
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def apply(mod, classes, deriving)
|
|
36
|
+
deriving.each do |cls|
|
|
37
|
+
case cls.to_s
|
|
38
|
+
when "Eq", "Show" then nil
|
|
39
|
+
when "Ord" then ord(mod, classes)
|
|
40
|
+
when "Enum" then enum(mod, classes)
|
|
41
|
+
when "Bounded" then bounded(mod, classes)
|
|
42
|
+
else
|
|
43
|
+
raise DataDeclarationError,
|
|
44
|
+
"cannot derive '#{cls}' for type '#{mod.type_name}' (supported: #{SUPPORTED.join(', ')})"
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Values compare by constructor order first, then field by field.
|
|
50
|
+
def ord(mod, classes)
|
|
51
|
+
tags = classes.each_with_index.to_h
|
|
52
|
+
type = mod
|
|
53
|
+
classes.each do |k|
|
|
54
|
+
k.include(Comparable)
|
|
55
|
+
k.define_method(:<=>) do |other|
|
|
56
|
+
return nil unless other.is_a?(Constructor) && other.class.data_type.equal?(type)
|
|
57
|
+
|
|
58
|
+
c = tags[self.class] <=> tags[other.class]
|
|
59
|
+
return c unless c.zero?
|
|
60
|
+
|
|
61
|
+
fields.zip(other.fields).each do |a, b|
|
|
62
|
+
r = a <=> b
|
|
63
|
+
return r if r.nil? || r != 0
|
|
64
|
+
end
|
|
65
|
+
0
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# `succ`, `pred`, ranges and `Type.enum_from*`; only for enumerations
|
|
71
|
+
# (every constructor nullary), as in GHC.
|
|
72
|
+
def enum(mod, classes)
|
|
73
|
+
unless classes.all?(&:nullary?)
|
|
74
|
+
raise DataDeclarationError,
|
|
75
|
+
"cannot derive 'Enum' for type '#{mod.type_name}': it must be an enumeration type (all constructors nullary)"
|
|
76
|
+
end
|
|
77
|
+
values = classes.map(&:value)
|
|
78
|
+
type_name = mod.type_name
|
|
79
|
+
values.each_with_index do |v, i|
|
|
80
|
+
k = v.class
|
|
81
|
+
k.define_method(:succ) do
|
|
82
|
+
values[i + 1] or raise ArgumentError, "#{type_name}.succ: bad argument (#{self} is the last value)"
|
|
83
|
+
end
|
|
84
|
+
k.define_method(:pred) do
|
|
85
|
+
i.positive? ? values[i - 1] : raise(ArgumentError, "#{type_name}.pred: bad argument (#{self} is the first value)")
|
|
86
|
+
end
|
|
87
|
+
k.define_method(:from_enum) { i }
|
|
88
|
+
k.alias_method(:to_i, :from_enum)
|
|
89
|
+
next if k.method_defined?(:<=>, false)
|
|
90
|
+
|
|
91
|
+
k.include(Comparable)
|
|
92
|
+
k.define_method(:<=>) do |other|
|
|
93
|
+
other.is_a?(Constructor) && other.class.data_type.equal?(mod) ? i <=> other.from_enum : nil
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
mod.define_singleton_method(:values) { values }
|
|
97
|
+
mod.define_singleton_method(:to_enum) do |i|
|
|
98
|
+
values[i] or raise ArgumentError, "#{type_name}.to_enum: bad argument #{i.inspect} (0..#{values.size - 1})"
|
|
99
|
+
end
|
|
100
|
+
mod.define_singleton_method(:from_enum) { |v| v.from_enum }
|
|
101
|
+
mod.define_singleton_method(:enum_from) { |v| values[v.from_enum..] }
|
|
102
|
+
mod.define_singleton_method(:enum_from_to) { |a, b| a > b ? [] : values[a.from_enum..b.from_enum] }
|
|
103
|
+
mod.define_singleton_method(:enum_from_then) do |a, b|
|
|
104
|
+
step = b.from_enum - a.from_enum
|
|
105
|
+
raise ArgumentError, "#{type_name}.enum_from_then: the two values must differ" if step.zero?
|
|
106
|
+
|
|
107
|
+
(a.from_enum..(step.positive? ? values.size - 1 : 0)).step(step.abs).map { |i| values[i] }
|
|
108
|
+
end
|
|
109
|
+
mod.define_singleton_method(:enum_from_then_to) do |a, b, c|
|
|
110
|
+
step = b.from_enum - a.from_enum
|
|
111
|
+
raise ArgumentError, "#{type_name}.enum_from_then_to: the first two values must differ" if step.zero?
|
|
112
|
+
|
|
113
|
+
a.from_enum.step(c.from_enum, step).map { |i| values[i] }
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
# `Type.min_bound` / `Type.max_bound`: the first and last constructor of
|
|
118
|
+
# an enumeration.
|
|
119
|
+
def bounded(mod, classes)
|
|
120
|
+
unless classes.all?(&:nullary?)
|
|
121
|
+
raise DataDeclarationError,
|
|
122
|
+
"cannot derive 'Bounded' for type '#{mod.type_name}': only enumeration types are supported"
|
|
123
|
+
end
|
|
124
|
+
first = classes.first.value
|
|
125
|
+
last = classes.last.value
|
|
126
|
+
mod.define_singleton_method(:min_bound) { first }
|
|
127
|
+
mod.define_singleton_method(:max_bound) { last }
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
end
|