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,71 @@
1
+ # frozen_string_literal: true
2
+
3
+ module HaskellMatch
4
+ # Mixin providing `fn`, `case_of`, `pattern`, `data` and `hdef`.
5
+ #
6
+ # class Calculator
7
+ # extend HaskellMatch::DSL
8
+ # include Maybe
9
+ #
10
+ # hdef :or_zero do
11
+ # on("Just x") { |x| x }
12
+ # on("Nothing") { 0 }
13
+ # end
14
+ # end
15
+ module DSL
16
+ def fn(name = nil, **options, &definition)
17
+ HaskellMatch.fn(name, **options, &definition)
18
+ end
19
+
20
+ def case_of(*values, **options, &definition)
21
+ HaskellMatch.case_of(*values, **options, &definition)
22
+ end
23
+
24
+ def pattern(source = nil, **options, &block)
25
+ HaskellMatch.pattern(source, **options, &block)
26
+ end
27
+
28
+ def data(decl, **options)
29
+ HaskellMatch.data(decl, **options)
30
+ end
31
+
32
+ # Define an instance method (when extended onto a class or module) or a
33
+ # singleton method (when `self` is any other object) by clauses. Clause
34
+ # bodies and guards run with `self` set to the receiver.
35
+ def hdef(name, exhaustive: HaskellMatch.exhaustive, overlapping: HaskellMatch.overlapping,
36
+ scope: Native::GLOBAL_SCOPE, &definition)
37
+ raise ArgumentError, "hdef needs a block with on(...) clauses" unless definition
38
+
39
+ builder = ClauseBuilder.collect(definition)
40
+ clauses = builder.clauses
41
+ matcher, _bodies, _guards = Compiler.compile(name, clauses, exhaustive: exhaustive, overlapping: overlapping,
42
+ scope: scope, resolver: builder.constant_resolver)
43
+ names = matcher.names
44
+ guards = clauses.map { |c| c.guard.equal?(ClauseBuilder::OTHERWISE) ? nil : c.guard }
45
+ plans = clauses.each_with_index.map do |c, i|
46
+ [BindingPlan.adapt(c.body, names[i], i, "body", name),
47
+ (guards[i] && BindingPlan.adapt(guards[i], names[i], i, "guard", name))]
48
+ end
49
+ has_guards = guards.any?
50
+
51
+ impl = lambda do |receiver, args|
52
+ guard_procs = nil
53
+ if has_guards
54
+ guard_procs = plans.map do |_, g|
55
+ next nil unless g
56
+
57
+ ->(*vals) { receiver.instance_exec(*vals, &g) }
58
+ end
59
+ end
60
+ index, values = matcher.select_with(args, guard_procs)
61
+ raise MatchError, "Non-exhaustive patterns in #{name}" if index.nil?
62
+
63
+ receiver.instance_exec(*values, &plans[index][0])
64
+ end
65
+
66
+ target = is_a?(Module) ? self : singleton_class
67
+ target.define_method(name) { |*args| impl.call(self, args) }
68
+ name
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,85 @@
1
+ # frozen_string_literal: true
2
+
3
+ module HaskellMatch
4
+ # Base class for every error raised by haskell_match.
5
+ class Error < StandardError; end
6
+
7
+ # Raised while compiling patterns (definition time).
8
+ class CompileError < Error; end
9
+
10
+ # Malformed pattern text.
11
+ class PatternSyntaxError < CompileError
12
+ # Reformat the native "msg (column N in \"src\")" into a message that
13
+ # shows the pattern with a caret under the column.
14
+ def self.with_caret(message)
15
+ m = message.match(/\A(.*) \(column (\d+) in (".*")\)\z/m)
16
+ return message unless m
17
+
18
+ src = begin
19
+ eval(m[3]) # rubocop:disable Security/Eval -- a Rust-escaped string literal
20
+ rescue SyntaxError, StandardError
21
+ return message
22
+ end
23
+ col = m[2].to_i
24
+ "#{m[1]}\n #{src}\n #{' ' * [col - 1, 0].max}^"
25
+ end
26
+ end
27
+
28
+ # A constructor name that no registered data type declares.
29
+ class UnknownConstructorError < CompileError; end
30
+
31
+ # A constructor applied to the wrong number of arguments.
32
+ class ArityError < CompileError; end
33
+
34
+ # Patterns of different types in the same position.
35
+ class PatternTypeError < CompileError; end
36
+
37
+ # The same variable bound twice in one clause.
38
+ class DuplicateVariableError < CompileError; end
39
+
40
+ # Record pattern problems (unknown field, positional constructor, ...).
41
+ class FieldError < CompileError; end
42
+
43
+ # Malformed `data` declaration.
44
+ class DataDeclarationError < CompileError; end
45
+
46
+ # Clauses of one function with different numbers of arguments.
47
+ class ClauseArityError < CompileError; end
48
+
49
+ # The clauses do not cover every possible value (GHC: -Wincomplete-patterns).
50
+ class NonExhaustiveError < CompileError
51
+ attr_reader :missing
52
+
53
+ def initialize(message, missing = [])
54
+ super(message)
55
+ @missing = missing
56
+ end
57
+ end
58
+
59
+ # A clause can never be selected (GHC: -Woverlapping-patterns).
60
+ class RedundantClauseError < CompileError
61
+ attr_reader :clauses
62
+
63
+ def initialize(message, clauses = [])
64
+ super(message)
65
+ @clauses = clauses
66
+ end
67
+ end
68
+
69
+ # Problems with the Ruby side of a definition (bad block parameters, ...).
70
+ class DefinitionError < CompileError; end
71
+
72
+ # Raised at match time.
73
+ class MatchError < Error; end
74
+
75
+ # A value is not of the type the patterns expect (what Haskell's type
76
+ # checker would have rejected statically).
77
+ class TypeMismatchError < MatchError; end
78
+
79
+ # An irrefutable (`~`) pattern failed to destructure after its clause was
80
+ # selected.
81
+ class IrrefutablePatternError < MatchError; end
82
+
83
+ # Recursion through compiled functions exceeded `HaskellMatch.max_depth`.
84
+ class StackOverflowError < Error; end
85
+ end
@@ -0,0 +1,140 @@
1
+ # frozen_string_literal: true
2
+
3
+ module HaskellMatch
4
+ # A constructor argument of the wrong type (`HaskellMatch.data` with
5
+ # `check_types: true`).
6
+ class FieldTypeError < Error; end
7
+
8
+ # Optional run-time checks of the field types written in a `data`
9
+ # declaration:
10
+ #
11
+ # HaskellMatch.data "Person = Person { name :: String, age :: Int }", check_types: true
12
+ # Person.new("Al", "3") # raises FieldTypeError: field 'age' expects Int, got "3" (String)
13
+ #
14
+ # Checks follow the declared Haskell type: `Int`/`Integer` take Integers,
15
+ # `Double`/`Float` any Numeric, `String` a String (or a list of
16
+ # characters), `Char` a one-character String, `Bool` true/false, `[a]` any
17
+ # list (Array, String, LazyList, Enumerator), `(a, b)` an Array of that
18
+ # size with each element checked, `a -> b` anything callable, a declared
19
+ # data type (`Maybe a`, `Shape`) a value of that type, any other
20
+ # capitalised name a Ruby class or module of that name (`Hash`, `Time`,
21
+ # `MyApp::Money`) when one exists, and a type variable anything.
22
+ module FieldTypes
23
+ module_function
24
+
25
+ # Wrap `klass`'s constructor so each field is checked against `types`.
26
+ def install(klass, types, scope)
27
+ checkers = types.map { |t| checker(t, scope) }
28
+ return if checkers.all?(&:nil?)
29
+
30
+ names = klass.members
31
+ cname = klass.constructor_name
32
+ klass.define_method(:initialize) do |**kw|
33
+ names.each_with_index do |n, i|
34
+ next unless kw.key?(n)
35
+
36
+ check = checkers[i]
37
+ next if check.nil? || check.call(kw[n])
38
+
39
+ raise FieldTypeError,
40
+ "#{cname}: field '#{n}' expects #{types[i]}, got #{kw[n].inspect} (#{kw[n].class})"
41
+ end
42
+ super(**kw)
43
+ end
44
+ end
45
+
46
+ LIST = ->(v) { v.is_a?(Array) || v.is_a?(String) || v.is_a?(LazyList) || v.is_a?(Enumerator) }
47
+
48
+ # A predicate for Haskell type text, or nil when the type is unchecked.
49
+ def checker(text, scope = Native::GLOBAL_SCOPE)
50
+ t = strip_parens(text.to_s.strip)
51
+ return nil if t.empty?
52
+ return ->(v) { v.respond_to?(:call) } if split_top(t, "->").size > 1
53
+
54
+ return ->(v) { v == [] } if t == "()"
55
+ if t.start_with?("(") && t.end_with?(")")
56
+ parts = split_top(t[1..-2], ",")
57
+ if parts.size > 1
58
+ subs = parts.map { |part| checker(part, scope) }
59
+ return lambda { |v|
60
+ v.is_a?(Array) && v.size == subs.size && subs.each_with_index.all? { |c, i| c.nil? || c.call(v[i]) }
61
+ }
62
+ end
63
+ end
64
+ return LIST if t.start_with?("[") && t.end_with?("]")
65
+ return nil if t.match?(/\A[a-z]/) # a type variable
66
+
67
+ head = t.split(/\s+/).first.sub(/\A!/, "")
68
+ case head
69
+ when "Int", "Integer", "Word", "Int8", "Int16", "Int32", "Int64", "Word8", "Word16", "Word32", "Word64", "Natural"
70
+ ->(v) { v.is_a?(Integer) }
71
+ when "Double", "Float", "Rational", "Num", "Real", "Fractional"
72
+ ->(v) { v.is_a?(Numeric) }
73
+ when "String", "Text"
74
+ ->(v) { v.is_a?(String) || v.is_a?(Array) || v.is_a?(LazyList) }
75
+ when "Char"
76
+ ->(v) { v.is_a?(String) && v.length == 1 }
77
+ when "Bool"
78
+ ->(v) { v == true || v == false }
79
+ when "Symbol"
80
+ ->(v) { v.is_a?(Symbol) }
81
+ else
82
+ mod = HaskellMatch.type_module(head, scope)
83
+ return ->(v) { mod === v } if mod # rubocop:disable Style/CaseEquality
84
+
85
+ const = ruby_constant(head)
86
+ const.is_a?(Module) ? ->(v) { v.is_a?(const) } : nil
87
+ end
88
+ end
89
+
90
+ def ruby_constant(name)
91
+ Object.const_get(name)
92
+ rescue NameError
93
+ nil
94
+ end
95
+
96
+ # `(T)` -> `T`, when the parentheses wrap the whole text.
97
+ def strip_parens(t)
98
+ while t.start_with?("(") && t.end_with?(")") && split_top(t[1..-2], ",").size == 1 && balanced?(t[1..-2])
99
+ t = t[1..-2].strip
100
+ end
101
+ t
102
+ end
103
+
104
+ def balanced?(t)
105
+ depth = 0
106
+ t.each_char do |c|
107
+ depth += 1 if "([".include?(c)
108
+ depth -= 1 if ")]".include?(c)
109
+ return false if depth.negative?
110
+ end
111
+ depth.zero?
112
+ end
113
+
114
+ # Split on `sep` at bracket depth zero.
115
+ def split_top(t, sep)
116
+ parts = []
117
+ depth = 0
118
+ cur = +""
119
+ i = 0
120
+ while i < t.length
121
+ c = t[i]
122
+ if "([".include?(c)
123
+ depth += 1
124
+ elsif ")]".include?(c)
125
+ depth -= 1
126
+ end
127
+ if depth.zero? && t[i, sep.length] == sep && !(sep == "," && i.zero?)
128
+ parts << cur.strip
129
+ cur = +""
130
+ i += sep.length
131
+ next
132
+ end
133
+ cur << c
134
+ i += 1
135
+ end
136
+ parts << cur.strip
137
+ parts
138
+ end
139
+ end
140
+ end
@@ -0,0 +1,240 @@
1
+ # frozen_string_literal: true
2
+
3
+ module HaskellMatch
4
+ # A function defined by pattern-matching clauses.
5
+ #
6
+ # length = HaskellMatch.fn(:length) do
7
+ # on("[]") { 0 }
8
+ # on("(_:xs)") { |xs| 1 + length.(xs) }
9
+ # end
10
+ # length.([1, 2, 3]) # => 3
11
+ # Returned by {Function#tail} and {Function#defer}: tells the native `call`
12
+ # to continue with `function` applied to `args`, and (for `defer`) to pass
13
+ # the eventual result to `continuation`. The native call keeps pending
14
+ # continuations on its own stack, so recursion written this way is bounded
15
+ # by memory rather than by Ruby's VM stack.
16
+ TailCall = Data.define(:function, :args, :continuation)
17
+
18
+ class Function < Native::Matcher
19
+ attr_reader :clauses
20
+
21
+ # Request a tail call: `recur.tail(n - 1, acc * n)` as the last
22
+ # expression of a clause body re-enters the function (or any other
23
+ # function) in constant stack space, like a tail call in Haskell.
24
+ def tail(*args)
25
+ TailCall.new(self, args, nil)
26
+ end
27
+
28
+ # Request a non-tail call whose result the block will receive:
29
+ # `length.defer(xs) { |n| 1 + n }` stands for `1 + length xs` and runs
30
+ # with the pending work kept off Ruby's stack, however deep it goes.
31
+ def defer(*args, &continuation)
32
+ raise ArgumentError, "defer needs a block to receive the result" unless continuation
33
+
34
+ TailCall.new(self, args, continuation)
35
+ end
36
+
37
+ # Build a function from clauses (use {HaskellMatch.fn}). The native
38
+ # `new` compiles the patterns; the clause bodies and guards are then
39
+ # attached as instance variables the native `call` reads on every call.
40
+ #
41
+ # With `ractor: true` the clause bodies and guards are made shareable
42
+ # (`Ractor.make_shareable`), as is the function, so it can be sent to and
43
+ # called from other Ractors. In that mode the definition block cannot
44
+ # call methods of the surrounding object, and local variables captured
45
+ # by the bodies must already be shareable; recursion goes through the
46
+ # function's name or `recur`, which work in both modes.
47
+ #
48
+ # With `deep: true` the function's `call` is implemented in Ruby (see
49
+ # {DeepCall}): about 100 ns slower per call, but deep recursion costs a
50
+ # tenth of the memory and the GC scans a fifth as much. The default is
51
+ # {HaskellMatch.deep_by_default}.
52
+ def self.define(name, builder, exhaustive:, overlapping:, ractor: false, deep: HaskellMatch.deep_by_default,
53
+ scope: Native::GLOBAL_SCOPE)
54
+ clauses = builder.clauses
55
+ clauses = make_shareable(clauses) if ractor
56
+ resolver = builder.respond_to?(:constant_resolver) ? builder.constant_resolver : nil
57
+ f, bodies, guards = Compiler.compile(name.to_s, clauses, exhaustive: exhaustive, overlapping: overlapping,
58
+ klass: self, scope: scope, resolver: resolver)
59
+ f.instance_variable_set(:@scope, scope)
60
+ f.instance_variable_set(:@helpers, builder.helpers.freeze)
61
+ f.extend(DeepCall.module_for(f.arity)) if deep
62
+ f.send(:attach, clauses, bodies, guards)
63
+ builder.define_function(f, name.to_s)
64
+ Ractor.make_shareable(f) if ractor
65
+ f
66
+ end
67
+
68
+ def self.make_shareable(clauses)
69
+ raise DefinitionError, "ractor: true requires Ractor support in this Ruby" unless defined?(Ractor)
70
+
71
+ clauses.map do |c|
72
+ body = Ractor.make_shareable(c.body)
73
+ guard = if c.guard.nil? || c.guard.equal?(ClauseBuilder::OTHERWISE)
74
+ c.guard
75
+ else
76
+ Ractor.make_shareable(c.guard)
77
+ end
78
+ Clause.new(c.patterns, guard, body, c.location)
79
+ end
80
+ end
81
+ private_class_method :make_shareable
82
+
83
+ # call(*args) is native (or Ruby, in deep mode); these make a Function
84
+ # behave like a Proc.
85
+ def [](*args)
86
+ call(*args)
87
+ end
88
+
89
+ def ===(*args)
90
+ call(*args)
91
+ end
92
+
93
+ # Whether this function runs in deep mode (see {DeepCall}).
94
+ def deep?
95
+ false
96
+ end
97
+
98
+ # A lambda with the function's exact arity. Built on first use (a
99
+ # shareable function cannot hold a reference back to itself).
100
+ def to_proc
101
+ return ProcBuilder.build(self, arity) if frozen?
102
+
103
+ @proc ||= ProcBuilder.build(self, arity)
104
+ end
105
+
106
+ def curry
107
+ to_proc.curry(arity)
108
+ end
109
+
110
+ # Composition, as for Procs: `(f >> g).(x)` is `g.(f.(x))` and
111
+ # `(f << g).(x)` is `f.(g.(x))` (Haskell's `g . f` and `f . g`).
112
+ def >>(other)
113
+ to_proc >> other
114
+ end
115
+
116
+ def <<(other)
117
+ to_proc << other
118
+ end
119
+
120
+ # The function as a curried Proc (memoised); what Haskell code receives
121
+ # when it passes a function as a value.
122
+ def curried
123
+ return to_proc if arity == 1
124
+ return @curried if defined?(@curried) && @curried
125
+
126
+ c = to_proc.curry(arity)
127
+ @curried = c unless frozen?
128
+ c
129
+ end
130
+
131
+ # Variables bound by each clause, in order.
132
+ def bindings
133
+ names
134
+ end
135
+
136
+ # The `where` helper functions defined inside this function, by name.
137
+ def helpers
138
+ @helpers || {}
139
+ end
140
+
141
+ # The type scope the patterns were compiled in.
142
+ def scope
143
+ @scope || Native::GLOBAL_SCOPE
144
+ end
145
+
146
+ # Human-readable dump of the compiled decision tree.
147
+ def decision_tree
148
+ tree
149
+ end
150
+
151
+ def inspect
152
+ "#<HaskellMatch::Function #{name}/#{arity}>"
153
+ end
154
+ alias to_s inspect
155
+
156
+ private
157
+
158
+ def attach(clauses, bodies, guards)
159
+ @clauses = clauses.freeze
160
+ @bodies = bodies.freeze
161
+ @guards = guards&.freeze
162
+ @proc = nil
163
+ end
164
+
165
+ # Builds the lambda returned by {Function#to_proc}. It is created here,
166
+ # rather than inside the function, so its `self` is this (shareable)
167
+ # module and `Ractor.make_shareable` accepts it.
168
+ module ProcBuilder
169
+ def self.build(function, arity)
170
+ params = (1..arity).map { |i| "a#{i}" }.join(", ")
171
+ # a lambda with the function's exact arity
172
+ eval("->(#{params}) { function.call(#{params}) }", binding, __FILE__, __LINE__) # rubocop:disable Style/EvalWithLocation
173
+ end
174
+ end
175
+ end
176
+
177
+ class << self
178
+ attr_writer :exhaustive, :overlapping
179
+
180
+ # Every this many nested calls the next clause body runs in a fresh Fiber
181
+ # (with its own VM and machine stacks), so plain recursion is bounded by
182
+ # memory rather than by Ruby's stack size. 0 disables this.
183
+ def stack_segment
184
+ Native.stack_segment
185
+ end
186
+
187
+ def stack_segment=(levels)
188
+ Native.stack_segment = levels
189
+ DeepCall.segment = Native.stack_segment
190
+ end
191
+
192
+ # Whether functions use deep mode unless told otherwise (default false).
193
+ attr_writer :deep_by_default
194
+
195
+ def deep_by_default
196
+ @deep_by_default ? true : false
197
+ end
198
+
199
+ # Deepest allowed nesting of calls (0 = unlimited); beyond it
200
+ # {StackOverflowError} is raised rather than letting a runaway recursion
201
+ # take all memory.
202
+ def max_depth
203
+ Native.max_depth
204
+ end
205
+
206
+ def max_depth=(levels)
207
+ Native.max_depth = levels
208
+ end
209
+
210
+ # Default policy for non-exhaustive clause sets: :error (Haskell's
211
+ # behaviour, the default), :warn or :ignore.
212
+ def exhaustive
213
+ @exhaustive.nil? ? :error : @exhaustive
214
+ end
215
+
216
+ # Default policy for redundant clauses: :error (default), :warn or :ignore.
217
+ def overlapping
218
+ @overlapping.nil? ? :error : @overlapping
219
+ end
220
+
221
+ # Define a function by clauses. See {Function}.
222
+ #
223
+ # Options:
224
+ # * `exhaustive:` true/:error (default), :warn, or false/:ignore
225
+ # * `overlapping:` true/:error (default), :warn, or false/:ignore
226
+ # * `ractor:` true to make the function Ractor-shareable (see {Function.define})
227
+ # * `deep:` true for Ruby-level body invocation (cheap deep recursion)
228
+ # * `scope:` the type scope the patterns resolve constructors in (see {HaskellMatch.new_scope})
229
+ def fn(name = nil, exhaustive: self.exhaustive, overlapping: self.overlapping, ractor: false,
230
+ deep: deep_by_default, scope: Native::GLOBAL_SCOPE, &definition)
231
+ raise ArgumentError, "HaskellMatch.fn needs a block with on(...) clauses" unless definition
232
+
233
+ options = { exhaustive: exhaustive, overlapping: overlapping, deep: deep, scope: scope }
234
+ builder = ClauseBuilder.collect(definition, ractor: ractor, options: options)
235
+ name ||= "anonymous function at #{definition.source_location&.join(':')}"
236
+ Function.define(name, builder, exhaustive: exhaustive, overlapping: overlapping, ractor: ractor, deep: deep,
237
+ scope: scope)
238
+ end
239
+ end
240
+ end